在日常的 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 秒
|