GCD (Grand Central Dispatch)
概念
关注两个概念:队列、任务。
iOS 多线程方案:pthread、NSThread、NSOperation、GCD
- 队列(Queue)和任务
GCD提供了dispatch queues来处理代码块,这些队列管理所提供给GCD的任务并用FIFO顺序执行这些任务。这样才能保证第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。
调度队列(dispath queue)是一个对象,它以 FIFO 的方式管理提交的任务。GCD有三种队列类型:
- 串行队列、
- 并行队列、
- 主队列
GCD中的任务只是一个代码块,它可以指一个block或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:
同步任务,使用dispatch_sync将任务加入队列;
异步任务,使用dispatch_async将任务加入队列。
串行(Serial)、并发(Concurrent)
串行(Serial),指同一时间每次只能执行一个任务。
并发(Concurrent),指同一时间可以同时执行多个任务。并发在异步(dispatch_async)函数下才有效。
同步、异步、并行 、串行
- 同步和异步决定了要不要开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力 - 并发和串行决定了任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
代码段
- dispatch_async
这种用法非常常见,比如开启一个异步的网络请求,待数据返回后返回主队列刷新UI;又比如请求图片,待图片返回刷新UI等
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 一个异步的任务,例如网络请求,耗时的文件操作
// do something
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新
// do something
});
});
- dispatch_after
// 在 1.6 秒后,将任务加入队列
NSTimeInterval delayTime = 1.6f;
dispatch_time_t delayTime_t = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(delayTime * NSEC_PER_SEC));
dispatch_after(delayTime_t,dispatch_get_main_queue(), ^(void){
// do something
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(delayTime * NSEC_PER_SEC)),dispatch_get_main_queue(), ^(void){
// do something
});
// 另一种常用实现
[self preformSelector:@selector(todoMethod:) withObject:nil afterDelay:5.f];
- dispatch_once
// 单例
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
// do something
});
- dispatch_group
dispatch_group_create 创建一个调度组,通过 dispatch_group_async 将任务分别添加到该组中,在组中的所有任务执行完成后回调(dispatch_group_notify)。
UIImageView *imageView1 = [[UIImageView alloc]init];
UIImageView *imageView2 = [[UIImageView alloc]init];
// 下载多张图片并且要求图片都下载完成才更新 UI
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步下载图片
dispatch_async(globalQueue, ^{
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 添加任务到 group
dispatch_group_async(group, globalQueue, ^{
NSURL *url = [NSURL URLWithString:@"pic URL"];
image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
});
dispatch_group_async(group, globalQueue, ^{
NSURL *url = [NSURL URLWithString:@"pic URL"];
image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
});
// group 执行完回调,在主线程更新 UI
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
imageView1.image = image1;
imageView2.image = image2;
});
});
- dispatch_apply
size_t times = 10;
dispatch_apply(times, dispatch_get_global_queue(0, 0), ^(size_t index){
// 执行10次代码,index顺序不确定
});
- dispatch_barrier_async
在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行;不能是全局的并发队列;所有的任务都必须在一个队列中。
// dispatch_barrier_async的作用是承上启下,保证此前的任务都先于自己执行,此后的任务也迟于自己执行。
// 本例中,任务4会在任务1、2、3都执行完之后执行,而任务5、6会等待任务4执行完后执行。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任务1
...
});
dispatch_async(queue, ^{
// 任务2
...
});
dispatch_async(queue, ^{
// 任务3
...
});
dispatch_barrier_async(queue, ^{
// 任务4
...
});
dispatch_async(queue, ^{
// 任务5
...
});
dispatch_async(queue, ^{
// 任务6
...
});
- UI 更新只能在主线程中进行
// 主线程刷新 tableView
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reload];
});
// 常用异步下载图片
UIImageView *imageView = [[UIImageView alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:@"pic URL"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// 回到主线程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
});
});
参考:
GCD日记
巧谈GCD
iOS笔记-多线程相关(pthread 、NSThread 、GCD、NSOperation)
由浅入深学习GCD☀️
GCD 深入理解(二)