1. GCD的基本使用
CGD是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核,自动管理线程的生命周期(创建线程、调度任务、销毁线程)。So,我们只需告诉CGD劳资想执行啥任务,不需要编写任何线程管理的代码。使用CGD来处理多线程问题爽的不要不要的😂
1.1 概念:
1.1.1 任务
任务就是执行什么操作。任务分为同步任务和异步任务,同步任务在当前线程中执行,异步任务在另一线程中执行。
// 同步方式执行任务
// queue:队列
// block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 异步方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
1.1.2 队列
队列就是存放任务的容器。队列分为串行队列和并行队列,并发队列可以让多个任务并发(即同时)执行(自动开启多个线程同时执行任务),so,并发任务只有在异步下才有效哦😄;串行队列中任务一个一个排队执行,跟我们排队打饭类似哦!
CGD的使用就下面两步:
- 定制任务(要干什么)
- 将任务添加到队列中。CGD会自动将队列中的任务取出放到对应的线程中执行,任务的取出遵循队列的FIFO原则。
下面给大家show一把这4种组合😜:
1.2. 串行队列 + 同步任务
/**
1. 创建一个串行队列
@param "lile" 队列标签
@param DISPATCH_QUEUE_SERIAL 队列的属性, serial:连续的,看来学好英文对编程很重要哦!
@return 串行队列
*/
dispatch_queue_t queue = dispatch_queue_create("lile", DISPATCH_QUEUE_SERIAL);
// 2. 同步执行任务
// 一般只要使用”同步“执行,串行队列对添加的同步任务,会立马执行
dispatch_sync(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
不开新线程,在当前线程(即主线程)顺序执行
1.3 串行队列 + 异步执行
// 1. 串行队列
// 下面两种写法是一样的
dispatch_queue_t queue1 = dispatch_queue_create("lile", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("lile", NULL);
// 2. 异步执行
for (int i = 0; i < 10; i++) {
dispatch_async(queue1, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
只会开一个线程(非主线程),而且所有任务都在这个新的线程里面执行
1.4 并行队列 + 同步执行
// 1. 并行队列,concurrent:并发的,同时发生的
dispatch_queue_t queue = dispatch_queue_create("lile", DISPATCH_QUEUE_CONCURRENT);
// 2. 同步执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
不开新线程,顺序一个一个执行
1.5 并行队列 + 异步执行
// 1. 并行队列
dispatch_queue_t queue = dispatch_queue_create("lile", DISPATCH_QUEUE_CONCURRENT);
// 2. 异步执行任务
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
会开很多个线程,同时执行
1.6 主队列 + 同步执行
// 1. 主队列:专门负责在主线程上调度任务,不会在子线程调度任务,在主队列不允许开新线程
dispatch_queue_t queue = dispatch_get_main_queue();
// 2. 同步执行任务
for (int i = 0; i < 10; i++) {
// 同步:把任务放到主队列里,但需是马上执行
dispatch_sync(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
[NSThread sleepForTimeInterval:2.0];
}
⚠️会发生死锁哦!
1.7 主队列 + 异步执行
// 1. 获得主队列-> 程序启动,--> 至少有一个主线程-> 一开始就会创建主队列
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"1----");
// 2. 异步执行任务
for (int i = 0; i < 10; i++) {
NSLog(@"调度前---");
// 异步:把任务放到主队列里,但是不需要马上执行
dispatch_async(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
[NSThread sleepForTimeInterval:2.0];
}
不开线程, 只能在主线程上面,顺序执行!
1.8 全局队列
// 获得全局队列
/**
参数:第一个参数,一般 写 0(可以适配 iOS 7 & 8)
iOS 7
DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
iOS 8
QOS_CLASS_DEFAULT 0
第二个参数:保留参数 0
*/
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 添加异步任务
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
开子线程,在子线程中无序执行
Tip: 全局队列和并行队列的区别:
全局队列没有名称,并发队列有名字
全局队列是供所有的应用程序共享
在在MRC开发,并发队列,创建完了,需要释放。 全局队列不需要我们管理
2. 一些特殊的使用场景
2.1 同步任务的使用场景
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("lile", DISPATCH_QUEUE_CONCURRENT);
/**
例子:有一个小说网站
- 必须登录,才能下载小说
有三个任务:
1. 用户登录
2. 下载小说A
3. 下载小说B
*/
// 添加任务
// 同步任务,需要马上执行。 不开新线程
dispatch_sync(queue, ^{
NSLog(@"用户登录 %@", [NSThread currentThread]);
});
// 异步任务,子线程中执行耗时操作
dispatch_async(queue, ^{
NSLog(@"下载小说A %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载小说B %@", [NSThread currentThread]);
});
关于队列的选择:
串行队列异步执行:开一条线程,顺序执行。慢,但资源占用少,省电。一般手机网络时使用,对性能要求不太高
并发队列异步执行:开启多条线程,同时执行。快,但资源消耗大,费电。一般在WI-FI环境或需要很快响应的场景下使用。
同步任务:一般只会在并发队列, 需要阻塞后续任务。必须等待同步任务执行完毕,再去执行其他任务。存在"依赖"关系。
2.2 延时操作
/**
NSEC_PER_SEC: 很大的数字
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
/**
多长时间后执行任务
@param when 表示从现在开始,经过多少纳秒以后
@param queue 在那个队列中执行任务
@param block 待执行的任务
*/
dispatch_after(when, dispatch_get_global_queue(0, 0), ^{
NSLog(@" %@", [NSThread currentThread]);
});
2.3 调度组(分组)
/**
应用场景:
开发的时候,有的时候出现多个网络请求都完成以后(每一个网络请求的事件长短不一定),再统一通知用户
比如: 下载小说:三国演义, 红楼梦, 金X梅
*/
// 实例化一个调度组
dispatch_group_t group = dispatch_group_create();
// 队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 任务添加到队列queue
dispatch_group_async(group, queue, ^{
NSLog(@"下载小说A---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下载小说B---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下载小说X---%@", [NSThread currentThread]);
});
// 获得所有调度组里面的异步任务完成的通知
dispatch_group_notify(group, queue, ^{
NSLog(@"下载完成,请观看%@", [NSThread currentThread]); // 异步的
});
//注意点: 在调度组完成通知里,可以跨队列通信
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 更新UI,在主线程
NSLog(@"下载完成,请观看%@", [NSThread currentThread]); // 异步的
});
2.4 一次性执行
多用于创建单例
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"%----ld", onceToken);
NSLog(@"真的执行一次么?");
});
NSLog(@"完成");
3. GCD中线程间的通信
// 全局队列 + 异步任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@", [NSThread currentThread]);
// 耗时操作: 放在全局队列,异步执行
// 1. url, 确定一个网络上的资源路径
NSURL *url = [NSURL URLWithString:@"http://fe.topit.me/e/d1/12/1170068721aa112d1el.jpg"];
// 2. 通过url可以下载对应的网络资源, 网络资源传输的都是二进制
NSData *data = [NSData dataWithContentsOfURL:url];
// 3. 二进制转成图片
UIImage *image = [UIImage imageWithData:data];
// 4. 更新UI,在主线程-》 直接把任务添加到主队列,就会在主队列执行
dispatch_async(dispatch_get_main_queue(), ^{
self.iconView.image = image;
NSLog(@"-----%@", [NSThread currentThread]);
});
});