【iOS 开发】多线程 GCD 的使用汇总
在日常的
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 秒后执行的代码"); // 更改秒数只要更改上面的数字 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); // 更改上面的数字 5 即可更改执行次数
});
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);
/* 创建定时器(dispatch_source_t 本质还是一个 OC 对象) */
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)); // 1 秒后开始执行
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); // 每隔 1 秒执行一次
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 秒
用法大概就这么多,有疑问或者不对的地方可以在下面留言,有需要的可以收藏一下。
将来的你,一定会感激现在拼命的自己,愿自己与读者的开发之路无限美好。