盒子
盒子
Posts List
  1. 前言
  2. Group的使用
  3. Dispatch Apply
  4. 总结

iOS GCD(Grand Central Dispatch)的使用(二)

作者Talent•C
转载请注明出处

前言

上篇文章主要以一些理论知识为主,主要介绍了 GCD 相关概念及简单的API用法,本篇文章主要以实践操作为主,主要介绍 dispatch groupdispatch apply

下面我们正式开始今天的任务。。。

Group的使用

相关API介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建group
dispatch_group_create();
//异步调度分组
dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue, dispatch_block_t block);
//异步调度分组另一种方式
dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);
//手动调度进组
dispatch_group_enter(dispatch_group_t group);
//手动调度出组
dispatch_group_leave(dispatch_group_t group);
//组等待 可借助其实现调度组同步执行
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
//异步调度组结果汇总
dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue, dispatch_block_t block);
//异步调度组结果汇总另一种方式
dispatch_group_notify_f(dispatch_group_t group,dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);

以上API基本涵盖了 GCD Group 的所有函数,下面我们来用他们实现一些具体功能。
1、分组执行一系列任务执行结果汇总输出
例如并发执行任务1~任务5最后将五个任务的总结果输出
实现方式一 自动调度组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
创建 并发队列talentQueue
参数 "TalentC.dispatch.queue.test" 队列的标记 可自定义
参数 DISPATCH_QUEUE_CONCURRENT并发队列 (同时可选则 DISPATCH_QUEUE_SERIAL 串行队列)
返回 dispatch_queue_t 队列对象 dispatch_object
*/
dispatch_queue_t talentQueue = dispatch_queue_create("TalentC.dispatch.queue.test", DISPATCH_QUEUE_CONCURRENT);
//创建调度组
dispatch_group_t talentGroup = dispatch_group_create();
//创建任务
for (int i = 1; i <= 5; i ++) {
dispatch_group_async(talentGroup, talentQueue, ^{
sleep(3);
NSString *string = [NSString stringWithFormat:@"任务%d",i];
NSLog(@"%@ 是否主线程:%@",string,[NSThread currentThread].isMainThread?@"YES":@"NO");
});
}
//分组结果通知
dispatch_group_notify(talentGroup, talentQueue, ^{
NSLog(@"thread: %p 是否主线程?:%@ 任务全部执行完毕*********",[NSThread currentThread],[NSThread currentThread].isMainThread?@"YES":@"NO");
});
NSLog(@"分组任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");

执行结果:

2017-04-06 15:28:16.692 GCD-Test[17383:8092341] 分组任务添加完毕 是否主线程:YES
2017-04-06 15:28:19.698 GCD-Test[17383:8092364] 任务1 是否主线程:NO
2017-04-06 15:28:19.698 GCD-Test[17383:8092365] 任务2 是否主线程:NO
2017-04-06 15:28:19.699 GCD-Test[17383:8092358] 任务3 是否主线程:NO
2017-04-06 15:28:19.699 GCD-Test[17383:8092368] 任务4 是否主线程:NO
2017-04-06 15:28:19.699 GCD-Test[17383:8092369] 任务5 是否主线程:NO
2017-04-06 15:28:19.699 GCD-Test[17383:8092369] thread: 0x12ed21b50 是否主线程?:NO 任务全部执行完毕*

注意: 这里的执行顺序不一定按照1~5执行因为 talentQueue 因为是并发线程如果为串行线程则会按照1~5的顺序进行。

实现方式二 手动调度组
只需要将上述中的 dispatch_group_async 修改成 dispatch_group_enter + dispatch_async + dispatch_group_leave 的组合即可,代码如下

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
/**
创建 并发队列talentQueue
参数 "TalentC.dispatch.queue.test" 队列的标记 可自定义
参数 DISPATCH_QUEUE_CONCURRENT 并发队列 (同时可选则 DISPATCH_QUEUE_SERIAL 串行队列)
返回 dispatch_queue_t 队列对象 dispatch_object
*/
dispatch_queue_t talentQueue = dispatch_queue_create("TalentC.dispatch.queue.test", DISPATCH_QUEUE_CONCURRENT);
//创建调度组
dispatch_group_t talentGroup = dispatch_group_create();
//创建任务
for (int i = 1; i <= 5; i ++) {
//进组
dispatch_group_enter(talentGroup);
//开启异步线程执行任务
dispatch_async(talentQueue, ^{
sleep(3);
NSString *string = [NSString stringWithFormat:@"任务%d",i];
NSLog(@"%@ 是否主线程:%@",string,[NSThread currentThread].isMainThread?@"YES":@"NO");
//任务执行完毕出组
dispatch_group_leave(talentGroup);
});
}
//分组结果通知
dispatch_group_notify(talentGroup, talentQueue, ^{
NSLog(@"thread: %p 是否主线程?:%@ 任务全部执行完毕*********",[NSThread currentThread],[NSThread currentThread].isMainThread?@"YES":@"NO");
});
NSLog(@"分组任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");

运行结果:

2017-04-06 15:38:26.260 GCD-Test[17391:8094544] 分组任务添加完毕 是否主线程:YES
2017-04-06 15:38:29.264 GCD-Test[17391:8094573] 任务1 是否主线程:NO
2017-04-06 15:38:29.264 GCD-Test[17391:8094577] 任务5 是否主线程:NO
2017-04-06 15:38:29.265 GCD-Test[17391:8094567] 任务2 是否主线程:NO
2017-04-06 15:38:29.266 GCD-Test[17391:8094572] 任务3 是否主线程:NO
2017-04-06 15:38:29.266 GCD-Test[17391:8094576] 任务4 是否主线程:NO
2017-04-06 15:38:29.266 GCD-Test[17391:8094576] thread: 0x13452ab30 是否主线程?:NO 任务全部执行完毕*

dispatch_group_async_f 和 dispatch_group_notify_f 的用法

先定义几个 C 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//任务1
void showStringForTask1 (void *obj) {
sleep(3);
NSString *objString = (__bridge NSString *)obj;
NSLog(@"异步调度组任务1:%@ 是否主线程?:%@",objString,[NSThread currentThread].isMainThread?@"YES":@"NO");
}
//任务2
void showStringForTask2 (void *obj) {
sleep(3);
NSString *objString = (__bridge NSString *)obj;
NSLog(@"异步调度组任务2:%@ 是否主线程?:%@",objString,[NSThread currentThread].isMainThread?@"YES":@"NO");
}
//任务3
void showStringForTask3 (void *obj) {
sleep(3);
NSString *objString = (__bridge NSString *)obj;
NSLog(@"异步调度组任务3:%@ 是否主线程?:%@",objString,[NSThread currentThread].isMainThread?@"YES":@"NO");
}
//全部任务结束回调
void allTaskNofify(void *obj)
{
NSLog(@"分组任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
创建 并发队列talentQueue
参数 "TalentC.dispatch.queue.test" 队列的标记 可自定义
参数 DISPATCH_QUEUE_CONCURRENT 并发队列 (同时可选则 DISPATCH_QUEUE_SERIAL 串行队列)
返回 dispatch_queue_t 队列对象 dispatch_object
*/
dispatch_queue_t talentQueue = dispatch_queue_create("TalentC.dispatch.queue.test", DISPATCH_QUEUE_CONCURRENT);
//创建调度组
dispatch_group_t talentGroup = dispatch_group_create();
/**
创建任务1
@"我是参数" 参数可以使任意数据类型
showStringForTask1 任务函数 函数类型可以查看dispatch_function_t
*/
dispatch_group_async_f(talentGroup, talentQueue, @"我是参数", showStringForTask1);
//创建任务2
dispatch_group_async_f(talentGroup, talentQueue, @"我是一只小蜜蜂哈哈哈", showStringForTask2);
//创建任务3
dispatch_group_async_f(talentGroup, talentQueue, @"再来一个任务吧", showStringForTask3);
//组任务结果汇总
dispatch_group_notify_f(talentGroup, talentQueue, nil, allTaskNofify);
NSLog(@"分组任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");

执行结果:

2017-04-06 16:04:50.116 GCD-Test[17428:8102843] 分组任务添加完毕 是否主线程:YES
2017-04-06 16:04:53.122 GCD-Test[17428:8102858] 异步调度组任务1:我是参数 是否主线程?:NO
2017-04-06 16:04:53.122 GCD-Test[17428:8102860] 异步调度组任务3:再来一个任务吧 是否主线程?:NO
2017-04-06 16:04:53.122 GCD-Test[17428:8102861] 异步调度组任务2:我是一只小蜜蜂哈哈哈 是否主线程?:NO
2017-04-06 16:04:53.123 GCD-Test[17428:8102861] 分组任务添加完毕 是否主线程:NO

2、等待群组任务完成disaptch_group_wait

1
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

同步等待事先提交到群组中的任务完成。在指定的超时期限过去之前,返回这些block是否完成。当发生超时时,这个群组将恢复到原来的状态。

  • 如果这个调度群组是空的(没有block与这个群组相关联),这个函数立即返回。
    在这个函数成功返回之后,这个调度群组是空的。它既可以使用 dispatch_release 释放掉,也可以重新添加block。
  • 成功(在指定的超时期限内,所有与群组相关联的block完成)将返回零。失败(超时发生)返回非零。
  • group,等待完成的调度群组。不可以为NULL。
  • timeout,超时时间(参考dispatch_time)。常量 DISPATCH_TIME_NOW (立即)和 DISPATCH_TIME_FOREVER (无穷大)被提供使用很方便。
    示例:
    我们将上面代码稍作修改子后面添加

    long ret = dispatch_group_wait(talentGroup, DISPATCH_TIME_FOREVER);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_group_async_f(talentGroup, talentQueue, @"我是参数", showStringForTask1);
//创建任务2
dispatch_group_async_f(talentGroup, talentQueue, @"我是一只小蜜蜂哈哈哈", showStringForTask2);
//创建任务3
dispatch_group_async_f(talentGroup, talentQueue, @"再来一个任务吧", showStringForTask3);
//组任务结果汇总
dispatch_group_notify_f(talentGroup, talentQueue, nil, allTaskNofify);
NSLog(@"分组任务添加完毕 是否主线程:%@",[NSThread currentThread].isMainThread?@"YES":@"NO");
long ret = dispatch_group_wait(talentGroup, DISPATCH_TIME_FOREVER);
if (ret == 0) {
NSLog(@"群组任务全部执行成功");
}else {
NSLog(@"群组任务全部执行失败|超时");
}

执行结果:

2017-04-06 16:22:28.435 GCD-Test[17433:8105482] 分组任务添加完毕 是否主线程:YES
2017-04-06 16:22:31.441 GCD-Test[17433:8105516] 异步调度组任务1:我是参数 是否主线程?:NO
2017-04-06 16:22:31.442 GCD-Test[17433:8105512] 异步调度组任务2:我是一只小蜜蜂哈哈哈 是否主线程?:NO
2017-04-06 16:22:31.442 GCD-Test[17433:8105510] 异步调度组任务3:再来一个任务吧 是否主线程?:NO
2017-04-06 16:22:31.442 GCD-Test[17433:8105482] 群组任务全部执行成功
2017-04-06 16:22:31.451 GCD-Test[17433:8105510] 分组任务添加完毕 是否主线程:NO

GCD Group的全部API及使用介绍完毕,个人可以按照实际需求自由组合。

Dispatch Apply

dispatch_apply
文档注释解释说:Submits a block to a dispatch queue for multiple invocations.
提交一个block块到一个分发的队里,以供多次调用.

This function submits a block to a dispatch queue for multiple invocations and waits for all iterations of the task block to complete before returning. If the target queue is a concurrent queue returned by dispatch_get_global_queue, the block can be invoked concurrently, and it must therefore be reentrant-safe. Using this function with a concurrent queue can be useful as an efficient parallel for loop.
The current index of iteration is passed to each invocation of the block.
摘自Apple 开发者文档

Apply 相关API

1
2
dispatch_apply(size_t iterations, dispatch_queue_t queue,DISPATCH_NOESCAPE void (^block)(size_t));
dispatch_apply_f(size_t iterations, dispatch_queue_t queue,void *_Nullable context,void (*work)(void *_Nullable, size_t));

一个任务执行5次 dispatch_apply
代码如下:

1
2
3
4
dispatch_queue_t talentQueue = dispatch_queue_create("TalentC.dispatch.queue.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, talentQueue, ^(size_t idx) {
NSLog(@"dispatch_apply 执行idx:%zu 是否主线程:%@",idx,[NSThread currentThread].isMainThread?@"YES":@"NO");
});

执行结果

2017-04-06 17:11:43.441 GCD-Test[17440:8111766] dispatch_apply 执行idx:0 是否主线程:YES
2017-04-06 17:11:43.441 GCD-Test[17440:8111786] dispatch_apply 执行idx:1 是否主线程:NO
2017-04-06 17:11:43.442 GCD-Test[17440:8111786] dispatch_apply 执行idx:3 是否主线程:NO
2017-04-06 17:11:43.442 GCD-Test[17440:8111786] dispatch_apply 执行idx:4 是否主线程:NO
2017-04-06 17:11:43.442 GCD-Test[17440:8111766] dispatch_apply 执行idx:2 是否主线程:YES

注意: 这里因为是并发线程所以不是按顺序输出,如果改成串行队列是可以按顺序输出的
dispatch_apply_f(size_t iterations, dispatch_queue_t queue,void *_Nullable context,void (*work)(void *_Nullable, size_t));
用法这里就不解释了与上述 dispatch_group_async_f 用法相似 只是参数多了一个。

总结

这里讲解的都是一些基础用法对于初学者足以,通过自己动敲一遍代码会加深理解,建议初学者可以跟着敲一遍,遇到问题及时查资料,这样可以做到举一反三,要知其然知其所以然;下篇文章会介绍队列挂起/恢复、barrier(栅栏)等。

支持一下
扫一扫,支持Talent•C