在日常的 iOS 开发中,关于多线程使用的较多的就是 Grand Central Dispatch(GCD) 了,GCD 会自动利用更多的 CPU 内核,会自动管理线程的生命周期,总之 GCD 的好处还是非常之多的,下面就对 GCD 的使用进行一个汇总。
GCD 的创建
GCD 中两个核心概念:队列 和 任务 ,一般基本的用法只需要创建这两步即可。
1. 队列的创建
队列有两种方式:串行队列 和 并发队列 。
- 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行,一个任务执行完毕后,再执行下一个任务。
| dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
 | 
- 并发队列(Concurrent Dispatch Queue):可以让多个任务同时一起执行,但是并发队列只有在- 异步(dispatch_async)函数下才有效。
| dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
 | 
2. 任务的创建
执行任务也有两种方式:同步执行 和 异步执行 。
- 同步执行(sync):等待线程任务完成之后,才会进行接下来的操作。
| dispatch_sync(queue, ^ {NSLog(@"同步执行的任务");
 });
 
 | 
- 异步执行(async):线程任务单独执行,不会影响接下来的操作。
| dispatch_async(queue, ^ {NSLog(@"异步执行的任务");
 });
 
 | 
GCD 的使用
在 iOS 开发过程中,我们一般在 主线程 里边进行 UI 刷新,例如:点击、滚动、拖拽 等事件,而耗时的操作则会放在 子线程 里边进行。而当我们有时候在 子线程 完成了耗时操作时,需要回到主线程,那么就用到了下面这个方法。
| dispatch_async(dispatch_get_main_queue(), ^ {NSLog(@"回到主线程");
 });
 
 | 
1. 串行队列 + 同步执行
| NSLog(@"----------- 开始 -----------");
 dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
 dispatch_sync(queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 1");
 }
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"任务 1 回到主线程");
 });
 });
 dispatch_sync(queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 2");
 }
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"任务 2 回到主线程");
 });
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
因为 串行队列 是按顺序执行任务,而 同步任务 又是等待线程任务完成之后,才会进行接下来的操作,所以基本和不使用多线程的效果是相同的,下面是打印结果:
| ----------- 开始 -----------任务 1
 任务 1
 任务 1
 任务 2
 任务 2
 任务 2
 ----------- 结束 -----------
 任务 1 回到主线程
 任务 2 回到主线程
 
 | 
2. 串行队列 + 异步执行
| NSLog(@"----------- 开始 -----------");
 dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
 dispatch_async(queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 1");
 }
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"任务 1 回到主线程");
 });
 });
 dispatch_async(queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 2");
 }
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"任务 2 回到主线程");
 });
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
由于 异步任务 是单独执行,所以不会影响到正常代码的执行,所以明显就和上面有区别了,下面是打印结果:
| ----------- 开始 ---------------------- 结束 -----------
 任务 1
 任务 1
 任务 1
 任务 2
 任务 2
 任务 2
 任务 1 回到主线程
 任务 2 回到主线程
 
 | 
3. 并发队列 + 异步执行
由于 并发队列 在 同步任务 中是无效的,并发队列 + 同步执行 和 串行队列 + 同步执行 没有任何区别,所以跳过直接看 并发队列 + 异步执行 。
| NSLog(@"----------- 开始 -----------");
 dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
 dispatch_async(queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 1");
 }
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"任务 1 回到主线程");
 });
 });
 dispatch_async(queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 2");
 }
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"任务 2 回到主线程");
 });
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
由于 并发队列 是多个任务同时执行的,所以可以看到 任务1 和 任务2 基本是同时执行的,下面是打印结果:
| ----------- 开始 ---------------------- 结束 -----------
 任务 1
 任务 2
 任务 1
 任务 2
 任务 1
 任务 2
 任务 1 回到主线程
 任务 2 回到主线程
 
 | 
4. notification 队列组(异步 + 并发)
上面 并发队列 + 异步执行 是各自任务完成后各自回到 主线程 。但是有时候我们会有这样的需求:分别异步执行两个耗时操作,然后当两个耗时操作都执行完毕后再回到 主线程 执行操作,这时候我们可以用到 GCD 的 notification 队列组。
| NSLog(@"----------- 开始 -----------");
 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 dispatch_group_t group = dispatch_group_create();
 dispatch_group_async(group, queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 1");
 }
 });
 dispatch_group_async(group, queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 2");
 }
 });
 
 
 dispatch_group_notify(group, dispatch_get_main_queue(), ^ {
 NSLog(@"回到主线程");
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
notification 队列组会等到两个任务都执行完才会回到主线程,下面是打印结果:
| ----------- 开始 ---------------------- 结束 -----------
 任务 1
 任务 2
 任务 1
 任务 2
 任务 1
 任务 2
 回到主线程
 
 | 
5. wait 队列组(同步 + 并发)
上面说过 并发队列 在 同步任务 中时无效的,但是 wait 队列组就能实现这个 同步 + 并发 效果。
| NSLog(@"----------- 开始 -----------");
 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 dispatch_group_t group = dispatch_group_create();
 dispatch_group_async(group, queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 1");
 }
 });
 dispatch_group_async(group, queue, ^ {
 for (int i=0; i<3; i++) {
 NSLog(@"任务 2");
 }
 });
 
 
 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
 
 
 dispatch_async(dispatch_get_main_queue(), ^ {
 NSLog(@"回到主线程");
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
可以看到任务是并发执行的,并且 结束 的打印在任务完成之后,下面是打印结果:
| ----------- 开始 -----------任务 2
 任务 1
 任务 2
 任务 1
 任务 2
 任务 1
 ----------- 结束 -----------
 回到主线程
 
 | 
6. 延时执行的代码
当我们需要延迟执行一段代码时,就可以用到 GCD 的 dispatch_after 方法。
| NSLog(@"----------- 开始 -----------");
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
 NSLog(@"延迟 3 秒后执行的代码");
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
这个方法是异步执行的,所以不会卡住界面,从时间可以看出延迟了 3 秒后才打印的,下面是打印结果:
| 2017-06-09 12:28:44.307 ----------- 开始 -----------2017-06-09 12:28:44.307 ----------- 结束 -----------
 2017-06-09 12:28:47.560 延迟 3 秒后执行的代码
 
 | 
7. 只执行一次的代码
使用下面方法执行的代码,在整个程序运行过程中,只会被执行一次,执行过一次以后就再也不会被执行,使用的是 GCD 的 dispatch_once 方法。
| static dispatch_once_t onceToken;dispatch_once(&onceToken, ^ {
 NSLog(@"只执行一次的代码");
 });
 
 | 
8. 同时执行多次的代码
通常我们会用 for 循环来使代码执行多次,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply ,使我们可以将代码执行多次。
| NSLog(@"----------- 开始 -----------");
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 dispatch_apply(5, queue, ^(size_t index) {
 NSLog(@"同时执行多次的代码 %zd",index);
 });
 
 NSLog(@"----------- 结束 -----------");
 
 | 
dispatch_apply 和 for 循环是有区别的,for 循环是按顺序依次执行多次,而 dispatch_apply 是同时执行多次,可以看到下面的 5 次几乎是同一时间执行的,下面是打印结果:
| ----------- 开始 -----------同时执行多次的代码 0
 同时执行多次的代码 2
 同时执行多次的代码 3
 同时执行多次的代码 1
 同时执行多次的代码 4
 ----------- 结束 -----------
 
 | 
9. 定时器
一般情况下,都是使用 NSTimer 来实现定时器,但是 GCD 其实也是可以实现定时器功能的,并且 GCD 定时器不受 RunLoop 约束,比 NSTimer 更加准时。
| @interface ViewController ()
 @property (nonatomic, strong) dispatch_source_t timer;
 
 @end
 
 | 
| NSLog(@"----------- 开始 -----------");
 __block NSInteger time = 0;
 
 
 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 
 
 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
 
 
 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
 uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
 dispatch_source_set_timer(self.timer, start, interval, 0);
 
 
 dispatch_source_set_event_handler(self.timer, ^{
 NSLog(@"计时 %ld 秒", ++time);
 if (time == 5) {
 
 dispatch_cancel(self.timer);
 self.timer = nil;
 }
 });
 
 
 dispatch_resume(self.timer);
 
 NSLog(@"----------- 结束 -----------");
 
 | 
下面是打印结果:
| 2017-06-09 14:55:37.894 ----------- 开始 -----------2017-06-09 14:55:37.894 ----------- 结束 -----------
 2017-06-09 14:55:38.896 计时 1 秒
 2017-06-09 14:55:39.895 计时 2 秒
 2017-06-09 14:55:40.895 计时 3 秒
 2017-06-09 14:55:41.895 计时 4 秒
 2017-06-09 14:55:42.895 计时 5 秒
 
 |