Dispatch Group总结

前段时间看叶孤城开源的下厨房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_asyncdispatch_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中的任务全部执行完毕)得到通知().

附注:asyncTaskWithNumbersyncTaskWithNumber的代码

- (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();
    });
}