有些时候,我们会想要一个同步的网络请求,而不用在AFN的回调里等待,层层嵌套。此外一些异步操作也希望按顺序来执行,
场景
之前手头上的项目有几个上传资料的页面,大概就是一个列表,用户一项项地选择资料(如图片)上传。
一开始我的做法是让用户全部选完,点击提交时,通过一些资料校验条件后开始上传,后来发现这样的体验有点问题,因为有些页面有十来个选项,平均每个选项两三张图片,而且由于后台限制,同时上传的线程不能超过2个,网络一般的情况下要等待10秒以上。
安卓那边的做法是,用户选完一项后立刻上传,为了不报错,会卡主线程,直到上传完毕,这样用户选完后至少会有一两秒的卡顿才能进行下一项操作。显然这样的处理方法也不尽如人意。
解决问题
只要做到按顺序执行异步操作即可,封装在工具类里,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| static dispatch_semaphore_t semaphore;
static dispatch_queue_t queue;
static dispatch_group_t group;
static bool isWait;
+ (dispatch_semaphore_t)semaphore { [self initSyncConfig]; return semaphore; }
+ (dispatch_queue_t)queue { [self initSyncConfig]; return queue; }
+ (dispatch_group_t)group { [self initSyncConfig]; return group; }
+ (void)initSyncConfig { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ semaphore = dispatch_semaphore_create(1); queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); group = dispatch_group_create(); }); }
#pragma mark - 异步顺序执行任务
+ (void)syncTask:(void (^)(dispatch_semaphore_t))operation { if (!operation) return; dispatch_group_async([self group], [self queue], ^{ dispatch_semaphore_wait([self semaphore], DISPATCH_TIME_FOREVER); operation([self semaphore]); dispatch_semaphore_wait([self semaphore], DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(semaphore); }); if (!isWait) { isWait = YES; dispatch_group_notify(group, queue, ^{ isWait = NO; XDLog(@"调度组任务完成"); }); } }
|
对外暴露 + (void)syncTask:(void (^)(dispatch_semaphore_t))operation
这个类方法,且主线程回调里必须执行 dispatch_semaphore_signal(semaphore);
。
原理
主要利用了信号量和调度组,把传过来的operation放到异步去执行,在异步里首先执行 dispatch_semaphore_wait([self semaphore], DISPATCH_TIME_FOREVER);
这样会使信号量减1,信号量本来是1,现在是0,往下走回再减1,信号量为-1,这时会卡住这个异步线程,后面的operation只进入调度组的队列,不能执行。
当异步的主线程回调里执行 dispatch_semaphore_signal(semaphore);
使信号量加1,让线程继续往下执行,往下还是 dispatch_semaphore_signal(semaphore);
执行完这个任务后信号量重新置为了1,下一个任务开始执行。
这样写可能还不够优雅,如果能hook住每一个任务的主线程回调,在里面执行dispatch_semaphore_signal(semaphore);
,而不是在每次调用这个方法在回调里执行,这样的封装会好很多。