这一篇其实在博客里已经发过了,这里凑个数再发一次。
前段时间看叶孤城开源的下厨房
app,在网络块看到它使用了Dispatch Group,想起来之前虽然看过一点但是从没用过,忘得差不多了,正好乘此机会再了解一下。而我们公司的app,最近正好有个需求我也用到了这个,自己这里做个总结。
Dispatch Group介绍和使用方法
下面是我查到的关于Dispatch Group的一个解释和作用说明
Dispatch Group,译作“派发分组”或“调度租”,是GCD的一种特性,能够把任务分组。调用者可以等待这组任务执行完成,也可以提供调用函数之后继续往下执行,这组任务完成是,调用者会得到通知。
关键点在任务分组和任务完成后得到通知,立马想到的一个需求场景是下载队列,将多个下载任务加到Dispatch Group中,全部下载完成后再执行相应的操作。下面看一下使用方法:
声明并创建一个Dispatch Group:
dispatch_group_t group = dispatch_group_create();
类似于创建派发队列,只是少了个标识符,然后将任务分组,这一步有两种方法
void dispatch_group_async(
dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
这种方法是dispatch_async函数的变体,多了一个参数,表示待执行块所属的的组,另一种分组的方法是一对函数:
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
enter可以是分组里正在执行的任务数加一,leave可以使之减一。由此易知这两个函数可以配对使用,类似内存管理的的retain和release。
下面这个函数则用于等待Dispatch Group任务执行完毕
long dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout);
这个函数接受两个参数,第一个表示等待的Group,第二个表示等待的时间,等待时会阻塞线程,此外也可以用DISPATCH_TIME_FORVER
表示永久等待.
除了上面所列的那个函数等待Dispatch Group执行完毕之外,也可以换个办法,使用下列函数:
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
与 wait
不同是可以可以传入queue和block,等Group上的执行完毕后在指定的queue中执行block,且这个不如阻塞线程,例如在其他线程处理数据,处理完成得到通知在主线程改变UI。
例如想在一个for循环中每次循环都执行一个方法,并在完成后得到通知,可以这样写
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_queue_t serviceQueue = dispatch_queue_create("com.serviceQueue.queue", NULL);
for (NSUInteger i = 0; i < TASKS_COUNT; ++i) {
dispatch_group_async(serviceGroup, serviceQueue, ^{
[object perfromTask];
});
}
// 亦可用wait代替
dispatch_group_notify(serviceGroup, serviceQueue, ^{
NSLog(@"Test 3 All tasks done dispatch_group_notify");
});
中间部分也可使用enter和leave代替:
for (NSUInteger i = 0; i < TASKS_COUNT; ++i) {
dispatch_group_enter(serviceGroup);
dispatch_async(serviceQueue, ^{
[object perfromTask];
});
}
上面基本介绍了Dispatch Group的使用方法,然而这其中还隐藏着一个坑。
Dispatch Group适用补充说明
这一句[object perfromTask];
,实际使用中发现performTask
是异步还是同步对结果有一定发现,仔细验证发现是
void dispatch_group_async(
dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
和
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
并不完全等价。
dispatch_group_async中执行的block为异步时没法得到正确的通知
先上代码,block为同步的情况
for (NSUInteger i = 0; i < TASKS_COUNT; ++i) {
dispatch_group_async(serviceGroup, serviceQueue, ^{
[self syncTaskWithNumber:i andCompletionBlock:^{
NSLog(@"Test 4 sync End of task %ld", i);
}];
});
}
dispatch_group_notify(serviceGroup, serviceQueue, ^{
NSLog(@"Test 4 sync All tasks done dispatch_group_notify");
});
结果如下
2016-01-28 16:07:17.204 DispatchGroup[10126:952831] Test 4 sync End of task 0
2016-01-28 16:07:17.205 DispatchGroup[10126:952831] Test 4 sync End of task 1
2016-01-28 16:07:17.205 DispatchGroup[10126:952831] Test 4 sync End of task 2
2016-01-28 16:07:17.205 DispatchGroup[10126:952831] Test 4 sync End of task 3
2016-01-28 16:07:17.206 DispatchGroup[10126:952831] Test 4 sync End of task 4
2016-01-28 16:07:17.206 DispatchGroup[10126:952831] Test 4 sync All tasks done dispatch_group_notify
结果如上所示,完全正常,那么再看看异步block的情况
for (NSUInteger i = 0; i < TASKS_COUNT; ++i) {
dispatch_group_async(serviceGroup, serviceQueue, ^{
[self asyncTaskWithNumber:i andCompletionBlock:^{
NSLog(@"Test 2 async End of task %ld", i);
}];
});
}
dispatch_group_notify(serviceGroup, serviceQueue, ^{
NSLog(@"Test 2 async All tasks done dispatch_group_notify");
});
结果如下,可以发现dispatch_group_notify
中的block被提前执行了
2016-01-28 16:10:08.403 DispatchGroup[10234:963964] Test 2 async End of task 0
2016-01-28 16:10:08.403 DispatchGroup[10234:963898] Test 2 async All tasks done dispatch_group_notify
2016-01-28 16:10:08.403 DispatchGroup[10234:964053] Test 2 async End of task 1
2016-01-28 16:10:08.403 DispatchGroup[10234:964054] Test 2 async End of task 2
2016-01-28 16:10:08.404 DispatchGroup[10234:964055] Test 2 async End of task 3
2016-01-28 16:10:08.404 DispatchGroup[10234:963964] Test 2 async End of task 4
那这是不是说Dispatch Group不适用异步block情况呢?请继续看dispatch_group_enter
的表示
for (NSUInteger i = 0; i < TASKS_COUNT; ++i) {
dispatch_group_enter(serviceGroup);
dispatch_async(serviceQueue, ^{
[self asyncTaskWithNumber:i andCompletionBlock:^{
NSLog(@"Test 5 End of task %ld", i);
dispatch_group_leave(serviceGroup);
}];
});
}
dispatch_group_notify(serviceGroup, serviceQueue, ^{
NSLog(@"Test 5 All tasks done dispatch_group_notify");
});
结果如下:
2016-01-28 16:14:24.815 DispatchGroup[10342:981474] Test 5 End of task 2
2016-01-28 16:14:24.815 DispatchGroup[10342:981440] Test 5 End of task 1
2016-01-28 16:14:24.815 DispatchGroup[10342:981438] Test 5 End of task 0
2016-01-28 16:14:24.815 DispatchGroup[10342:981316] Test 5 End of task 3
2016-01-28 16:14:24.815 DispatchGroup[10342:981475] Test 5 End of task 4
2016-01-28 16:14:24.816 DispatchGroup[10342:981316] Test 5 All tasks done dispatch_group_notify
完全OK,也即是说异步情况只能用
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
总结
Dispatch Group可以用于管理过个任务,可以用dispatch_group_async
和dispatch_group_enter dispatch_group_enter
两种方式将任务添加到Dispatch Group中,并且可以在在任务执行完成后得到通知,执行dispatch_group_notify
中的block,亦可用dispatch_group_wait
阻塞等待任务执行完成.
但是当任务需要异步执行的时候,dispatch_group_enter dispatch_group_enter
才能确保dispatch_group_notify
是在正确的时候(即Dispatch Group中的任务全部执行完毕)得到通知().
附注:asyncTaskWithNumber
和syncTaskWithNumber
的代码
- (void)asyncTaskWithNumber:(NSUInteger)number andCompletionBlock:(void (^)(void))completionBlock
{
usleep(arc4random_uniform(SLEEP_TIME));
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
usleep(arc4random_uniform(SLEEP_TIME));
completionBlock();
});
}
- (void)syncTaskWithNumber:(NSUInteger)number andCompletionBlock:(void (^)(void))completionBlock
{
usleep(arc4random_uniform(SLEEP_TIME));
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
usleep(arc4random_uniform(SLEEP_TIME));
completionBlock();
});
}
谢谢阅读。
___
我是 Wythe,iOS 开发者,对其他技术也有好奇。公众号 WytheTalk,从一个程序员的角度看世界,主要是技术分享,也有对互联网各种事的观点。欢迎关注。
![WytheTalk.jpg](http://upload-images.jianshu.io/upload_images/446839-6e1ec13cf518d34c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)