<导语>
GCD是C语言的,C语言里面创建一个东西出来通过函数实现多线程技术,GCD里面所的函数都是dispatch(调度) 开头
说明:
本文中所有比较完整的代码的Demo,可以到github中下载,链接github
- 将
任务
添加到队列
,并且指定执行任务的函数
- 任务使用
block
封装- 任务的
block
没有参数也没有返回值
- 任务的
- 执行任务的函数
- 异步
dispatch_async
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行
block
的任务 -
异步
是多线程的代名词
- 同步
dispatch_sync
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前执行
block
的任务
- 异步
- 队列 - 负责调度任务
- 第一类:需要我们自己创建,
- 1> 串行队列
- 一次只能"调度"一个任务
dispatch_queue_create("itheima", NULL);
- 2> 并发队列
- 一次可以"调度"多个任务
dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
- 第二类:由系统提供
- 1> 全局队列
- 就等于并发队列
- 并发队列,需要我们程序员自己去创建
- 全局队列是系统提供
- 2> 主队列
- 是一个奇葩
- 主要用于回到主线程,所有代码在主线程执行
- 专门用来在主线程上调度任务的队列
- 不会开启线程
- 在
主线程空闲时
才会调度队列中的任务在主线程执行 dispatch_get_main_queue();
<阶段性小结>
-
开不开线程由
执行任务的函数
决定-
异步
开,异步
是多线程的代名词 -
同步
不开
-
-
开几条线程由
队列
决定-
串行队列
开一条线程 -
并发队列
开多条线程,具体能开的线程数量由底层线程池决定- iOS 8.0 之后,GCD 能够开启非常多的线程
- iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
-
队列的选择
- 多线程的目的:将耗时的操作放在后台执行!
- 串行队列,只开一条线程,所有任务顺序执行
- 如果任务有先后执行顺序的要求
- 效率低 -> 执行慢 -> "省电"
- 有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"
- 并发队列,会开启多条线程,所有任务不按照顺序执行
- 如果任务没有先后执行顺序的要求
- 效率高 -> 执行快 -> "费电"
- WIFI,包月
- 实际开发中,线程数量如何决定?
- WIFI 线程数
6
条 - 3G / 4G 移动开发的时候,
2~3
条,再多会费电费钱!
- 线程、队列与任务的关系
- 任务:必须要有一个依靠,这个依靠就是线程,虽然我们的任务是由我们的队列去调度,但是执行必须在线程上
- 线程:它就是App(进程)的最小执行单元
- 队列与任务:.任务会加入到队列中,然后由队列去调度,然后去执行
- 线程和任务:任务必须执行在一个线程上
<同步&异步的写法>
一. 概念
- 同步
- 在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句
- 异步
- 不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句
二. NSThread 中的同步 & 异步
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"start");
// 同步执行
// [self demo];
// 异步执行
[self performSelectorInBackground:@selector(demo) withObject:nil];
NSLog(@"over");
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
NSLog(@"demo 完成");
}
三. GCD中的同步&异步
#pragma mark - 同步和异步的写法示例
/**
* 1. 在触摸开始事件中展示同步
*
* 2. 在触摸结束事件中展示异步
*/
/**
* 触摸开始事件
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 1.GCD同步方法
/**
参数1:队列
参数2:任务
*/
NSLog(@"touch-begin-begin");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"---gcd ---sync,%@", [NSThread currentThread]);
});
NSLog(@"touch-begin-end");
/**
同步的打印顺序
打印 touch-begin-begin
打印 ---gcd ---sync
打印 touch-begin-end
同步任务执行的线程:主线程
{number = 1, name = main}
*/
}
/**
* 触摸结束事件
*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.GCD异步方法
/**
参数1:队列
参数2:任务
异步:不会在`当前线程`(主线程)执行
既然是异步去执行,那么就必须先去开线程,但是开线程会花费时间
*/
NSLog(@"touch-end-begin");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"---gcd ---async, %@",[NSThread currentThread]);
});
NSLog(@"touch-end-end");
/**
异步的打印顺序
打印 touch-end-begin
打印 touch-end-end
打印 ---gcd ---async
异步任务执行的线程:子线程
{number = 2, name = (null)}
*/
}
<串行队列的同步和异步>
一. 特点
- 以
先进先出
的方式,顺序
调度队列中的任务执行 - 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务
二. 队列创建
dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("[email protected]", NULL);
三. 串行队列演练
1. 串行队列, 同步执行
- 打印顺序 : 从上到下,依次打印,因为串行的
- 在哪条线程上执行:主线程,因为是同步方法,所以在当前线程里面执行,恰好当前线程是主线程,所以它就在主线程上面执行
- 应用场景:开发中很少用
/**
* 触摸开始事件:展示 串行队列的同步 执行结果
* 1. 创建一个同步方法,在方法中
* > 创建一个串行队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸开始事件中调用同步方法
* 3. 输出结果如下
* > task1==={number = 1, name = main}
* > task2==={number = 1, name = main}
* > task3==={number = 1, name = main}
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用同步方法
[self serialSync];
}
/**
* 串行队列同步方法
*/
- (void)serialSync {
// 1.创建一个串行队列
// > 参数1:队列的标识符号,一般是公司的域名倒写(const char *label)
// > 参数2:队列的类型(dispatch_queue_attr_t attr)
// DISPATCH_QUEUE_SERIAL 串行队列
// DISPATCH_QUEUE_CONCURRENT 并发队列
dispatch_queue_t serialQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_SERIAL);
// 2.创建三个任务
void (^task1) () = ^(){
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
// > 参数1:前面所创建的队列(dispatch_queue_t queue)
// > 参数2:block代码块,即创建的三个任务(^{
//code
//})
dispatch_sync(serialQueue, task1);
dispatch_sync(serialQueue, task2);
dispatch_sync(serialQueue, task3);
}
2. 串行队列, 异步执行
- 打印顺序:从上到下依次执行,它是串行队列
- 在哪条线程上执行:在子线程,因为它是异步执行,异步,就是不在当前线程里面执行
- 应用场景:耗时间,有顺序的任务---1.登录--->2.付费--->3.才能看
/**
* 触摸结束事件:展示 串行队列的异步 执行结果
* 1. 创建一个同步方法,在方法中
* > 创建一个串行队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸结束事件中调用异步方法
* 3. 输出结果如下
* > task1==={number = 4, name = (null)}
* > task2==={number = 4, name = (null)}
* > task3==={number = 4, name = (null)}
*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用异步方法
[self serialAsync];
}
/**
* 1. 创建串行队列的异步方法
*/
- (void)serialAsync {
// 1.创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_SERIAL);
// 2.创建任务
void (^task1) () = ^() {
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^() {
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^() {
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.用异步方法把任务添加到队列中
dispatch_async(serialQueue, task1);
dispatch_async(serialQueue, task2);
dispatch_async(serialQueue, task3);
}
<并发队列的同步和异步>
一. 特点
- 以
先进先出
的方式,并发
调度队列中的任务执行 - 如果当前调度的任务是
同步
执行的,会等待任务执行完成后,再调度后续的任务 - 如果当前调度的任务是
异步
执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行
二. 队列创建
dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);
三. 并发队列演练
1. 并发队列,同步执行
- 打印顺序: 依次执行,因为它是同步的
- 在哪条线程上执行:主线程,因为它是同步方法,它就在当前线程里面执行,主线程,依次执行
- 当它遇到同步的时候,并发队列,还是依次执行
- 所以说,方法的优先级会比队列的优先级高
- 只要是同步方法,都只会在当前线程里面执行,不会开子线程
- 应用场景: 开发中几乎不用
/**
* 触摸开始事件:展示 并发队列的同步 执行结果
* 1. 创建一个同步方法,在方法中
* > 创建一个并发队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸开始事件中调用同步方法
* 3. 输出结果如下
* > task1==={number = 1, name = main}
* > task2==={number = 1, name = main}
* > task3==={number = 1, name = main}
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用同步方法
[self conCurrentSync];
}
/**
* 1. 创建并发队列同步方法
*/
- (void)conCurrentSync {
// 1.创建一个并发队列
// > 参数1:队列的标识符号,一般是公司的域名倒写(const char *label)
// > 参数2:队列的类型(dispatch_queue_attr_t attr)
// DISPATCH_QUEUE_SERIAL 串行队列
// DISPATCH_QUEUE_CONCURRENT 并发队列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);
// 2.创建三个任务
void (^task1) () = ^(){
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
// > 参数1:前面所创建的队列(dispatch_queue_t queue)
// > 参数2:block代码块,即创建的三个任务(^{
//code
//})
dispatch_sync(conCurrentQueue, task1);
dispatch_sync(conCurrentQueue, task2);
dispatch_sync(conCurrentQueue, task3);
}
2. 并发队列,异步执行
- 打印顺序: 无序的
- 在哪条线程上执行:在子线程上执行,第一个任务,都在它自己的线程上执行开N条,它是由底层可调度线程池来决定的,可调度线程池它是有一个重用机制
- 应用场景: 下载电视剧某一集的时候,可以把我们的片头,片尾,中间内容 一起下;最后,拼接组合一下,就可以播放了;片头,中间内容,片尾
/**
* 触摸结束事件:展示 并发队列的异步 执行结果
* 1. 创建一个异步方法,在方法中
* > 创建一个并发队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸结束事件中调用异步方法
* 3. 输出结果如下
* > task2==={number = 3, name = (null)}
* > task1==={number = 2, name = (null)}
* > task3==={number = 4, name = (null)}
*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用异步方法
[self conCurrentAsync];
}
/**
* 1. 创建并发队列的异步方法
*/
- (void)conCurrentAsync {
// 1.创建并发队列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);
// 2.创建任务
void (^task1) () = ^() {
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^() {
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^() {
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.用异步方法把任务添加到队列中
dispatch_async(conCurrentQueue, task1);
dispatch_async(conCurrentQueue, task2);
dispatch_async(conCurrentQueue, task3);
}
<全局队列的同步和异步>
一. 概念
- 是系统为了方便程序员开发提供的,其工作表现与
并发队列
一致
二. 全局队列 & 并发队列的区别
- 全局队列
- 没有名称
- 无论 MRC & ARC 都不需要考虑释放
- 日常开发中,建议使用"全局队列"
- 并发队列
- 有名字,和
NSThread
的name
属性作用类似 - 如果在 MRC 开发时,需要使用
dispatch_release(q);
释放相应的对象 -
dispatch_barrier
必须使用自定义的并发队列 - 开发第三方框架时,建议使用并发队列
- 有名字,和
三. 代码演示与总结
1. 全局队列 异步任务
- 打印顺序: 依次执行,因为它是同步的
- 在哪条线程上执行:主线程,因为它是同步方法,它就在当前线程里面执行,主线程,依次执行
- 当它遇到同步的时候,并发队列,还是依次执行
- 所以说,方法的优先级会比队列的优先级高
- 只要是同步方法,都只会在当前线程里面执行,不会开子线程
- 应用场景: 开发中几乎不用
/**
* 触摸开始事件:展示 全局队列的同步 执行结果
* 1. 创建一个同步方法,在方法中
* > 获取全局队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸开始事件中调用同步方法
* 3. 输出结果如下
* > task1==={number = 1, name = main}
* > task2==={number = 1, name = main}
* > task3==={number = 1, name = main}
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用同步方法
[self globalSync];
}
/**
* 全局队列同步方法
*/
- (void)globalSync {
// 1.获取全局队列
// > 参数1: iOS7表示的优先级; iOS8表示服务质量; 为了保证兼容iOS7&iOS8一般传入0
// > 参数2: 未来使用,传入0
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
// 2.创建三个任务
void (^task1) () = ^(){
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
// > 参数1:前面所获取的队列(dispatch_queue_t queue)
// > 参数2:block代码块,即创建的三个任务(^{
//code
//})
dispatch_sync(globalQueue, task1);
dispatch_sync(globalQueue, task2);
dispatch_sync(globalQueue, task3);
}
2. 并发队列,异步方法
- 打印顺序: 无序的
- 在哪条线程上执行: 在子线程上执行,第一个任务,都在它自己的线程上执行开N条,它是由底层可调度线程池来决定的,可调度线程池它是有一个重用机制
- 应用场景: 下载电视剧某一集的时候,可以把我们的片头,片尾,中间内容 一起下;最后,拼接组合一下,就可以播放了;片头,中间内容,片尾
/**
* 触摸结束事件:展示 全局队列的异步 执行结果
* 1. 创建一个异步方法,在方法中
* > 获取全局队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸结束事件中调用异步方法
* 3. 输出结果如下
* > task2==={number = 3, name = (null)}
* > task1==={number = 2, name = (null)}
* > task3==={number = 4, name = (null)}
*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用异步方法
[self globalAsync];
}
/**
* 并发队列的异步方法
*/
- (void)globalAsync {
// 1.获取全局队列
// > 参数1: iOS7表示的优先级; iOS8表示服务质量; 为了保证兼容iOS7&iOS8一般传入0
// > 参数2: 未来使用,传入0
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
// 2.创建任务
void (^task1) () = ^() {
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^() {
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^() {
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.用异步方法把任务添加到队列中
dispatch_async(globalQueue, task1);
dispatch_async(globalQueue, task2);
dispatch_async(globalQueue, task3);
}
运行效果与并发队列相同
3. 参数解析
-
服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级
- iOS 8.0(新增,暂时不能用,今年年底)
-
QOS_CLASS_USER_INTERACTIVE
0x21, 用户交互(希望最快完成-不能用太耗时的操作) -
QOS_CLASS_USER_INITIATED
0x19, 用户期望(希望快,也不能太耗时) -
QOS_CLASS_DEFAULT
0x15, 默认(用来底层重置队列使用的,不是给程序员用的) -
QOS_CLASS_UTILITY
0x11, 实用工具(专门用来处理耗时操作!) -
QOS_CLASS_BACKGROUND
0x09, 后台 -
QOS_CLASS_UNSPECIFIED
0x00, 未指定,可以和iOS 7.0 适配
-
- iOS 7.0
-
DISPATCH_QUEUE_PRIORITY_HIGH
2 高优先级 -
DISPATCH_QUEUE_PRIORITY_DEFAULT
0 默认优先级 -
DISPATCH_QUEUE_PRIORITY_LOW
(-2) 低优先级 -
DISPATCH_QUEUE_PRIORITY_BACKGROUND
INT16_MIN 后台优先级
-
- iOS 8.0(新增,暂时不能用,今年年底)
为未来保留使用的,应该永远传入0
注意:如果要适配 iOS 7.0 & 8.0,使用以下代码:
dispatch_get_global_queue(0, 0);
<主队列的同步和异步>
一. 特点
- 专门用来在主线程上调度任务的队列
- 不会开启线程
- 以
先进先出
的方式,在主线程空闲时
才会调度队列中的任务在主线程执行 - 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
二. 队列获取
- 主队列是负责在主线程调度任务的
- 会随着程序启动一起创建
- 主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();
三. 代码演示
1. 主队列,同步方法
- 主队列,同步任务有问题,不能用
- 虽然进入到了同步方法,但并没有继续执行
- 原因:
主队列
只有在主线程空闲
的时候,才会去调度它里面的任务去执行 - 因为是同步方法,只能顺序执行,同步方法在等主队列任务的执行,而主队列任务在等同步方法的执行
- 此处,程序进入了死等状态
- 原因:
/**
* 触摸开始事件:展示 主队列的同步 执行结果
* 1. 创建一个同步方法,在方法中
* > 添加输出,验证来到同步方法了
* > 获取主队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
* 2. 在触摸开始事件中调用同步方法
* 3. 输出结果如下
* > [ViewController mainSync]
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用同步方法(如果调用,程序会进入死等)
//[self mainSync];
}
/**
* 主队列同步方法
*/
- (void)mainSync {
NSLog(@"%s",__func__);
// 1.获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 2.创建三个任务
void (^task1) () = ^(){
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
// > 参数1:前面所获取的队列(dispatch_queue_t queue)
// > 参数2:block代码块,即创建的三个任务(^{
//code
//})
dispatch_sync(mainQueue, task1);
dispatch_sync(mainQueue, task2);
dispatch_sync(mainQueue, task3);
}
2. 主队列,异步方法
/**
* 触摸结束事件:展示 主队列的异步 执行结果
* 1. 创建一个异步方法,在方法中
* > 获取主队列
* > 创建三个任务,每个任务都输出当前任务名及执行的线程
* > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
* > 添加输出,验证主队列执行顺序
* 2. 在触摸结束事件中调用异步方法
* 3. 输出结果如下
* > task1==={number = 1, name = main}
* > task2==={number = 1, name = main}
* > task3==={number = 1, name = main}
*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 2.调用异步方法
[self mainAsync];
}
/**
* 主队列的异步方法
*/
- (void)mainAsync {
// 1.获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 2.创建任务
void (^task1) () = ^() {
NSLog(@"task1===%@", [NSThread currentThread]);
};
void (^task2) () = ^() {
NSLog(@"task2===%@", [NSThread currentThread]);
};
void (^task3) () = ^() {
NSLog(@"task3===%@", [NSThread currentThread]);
};
// 3.用异步方法把任务添加到队列中
dispatch_async(mainQueue, task1);
dispatch_async(mainQueue, task2);
dispatch_async(mainQueue, task3);
// 4.验证任务执行顺序
NSLog(@"---mainAsync---");
}
一. 异步执行任务
- (void)gcdDemo1 {
// 1. 全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 任务
void (^task)() = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 3. 指定执行任务的函数
// 异步执行任务 - 新建线程,在新线程执行 task
dispatch_async(q, task);
NSLog(@"come here");
}
注意:如果等待时间长一些,会发现线程的
number
发生变化,由此可以推断gcd 底层线程池
的工作
二. 同步执行任务
- (void)gcdDemo1 {
// 1. 全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 任务
void (^task)() = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 3. 指定执行任务的函数
// 同步执行任务 - 不开启线程,在当前线程执行 task
dispatch_sync(q, task);
NSLog(@"come here");
}
三. 精简代码
- (void)gcdDemo2 {
for (int i = 0; i < 10; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@ %@", [NSThread currentThread], @"hello");
});
}
}
与 NSThread
的对比
- 所有的代码写在一起的,让代码更加简单,易于阅读和维护
-
NSThread
通过@selector
指定要执行的方法,代码分散 -
GCD
通过block
指定要执行的代码,代码集中
-
- 使用
GCD
不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期 - 如果要开多个线程
NSThread
必须实例化多个线程对象 -
NSThread
靠NSObject
的分类方法实现的线程间通讯,GCD
靠block
四. 线程间通讯
- (void)gcdDemo3 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"耗时操作 %@", [NSThread currentThread]);
// 耗时操作之后,更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新 UI %@", [NSThread currentThread]);
});
});
}
以上代码是
GCD
最常用代码组合!
- 如果要在更新 UI 之后,继续做些事情,可以使用以下代码
- (void)gcdDemo4 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"耗时操作");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
NSLog(@"更新UI完毕");
});
}
五. 网络下载图片
- (void)viewDidLoad {
[super viewDidLoad];
// 1.添加图片框
[self setupImageView];
}
/**
* 添加图片框
*/
- (void)setupImageView {
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(100, 200, 300, 200);
[self.view addSubview:imageView];
_imageView = imageView;
}
/**
* 触摸事件
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 1.全局队列,异步方法
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 下载网络图片
NSLog(@"%@==下载图片", [NSThread currentThread]);
NSURL *url = [NSURL URLWithString:@"http://g.hiphotos.baidu.com/image/pic/item/f31fbe096b63f624cd2991e98344ebf81b4ca3e0.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
[NSThread sleepForTimeInterval:3];
// 2.主队列,异步方法
dispatch_async(dispatch_get_main_queue(), ^{
// 更新UI
NSLog(@"%@==更新UI", [NSThread currentThread]);
self.imageView.image = image;
});
});
}
只有异步方法,才具备开启线程的能力
主队列是个奇葩--主队列使用的时候,千万不要使用同步方法,使用异步方法也不会开子线程,因为主队列只在主线程里面执行
<同步任务的作用>
同步任务,可以让其他异步执行的任务,
依赖
某一个同步任务
例如:在用户登录之后,再异步下载文件!
- (void)gcdDemo1 {
dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"登录 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载 A %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载 B %@", [NSThread currentThread]);
});
}
- 代码改造,让登录也在异步执行
- (void)gcdDemo2 {
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);
void (^task)() = ^{
// 同步
dispatch_sync(queue, ^{
NSLog(@"登录 %@", [NSThread currentThread]);
});
// 异步
dispatch_async(queue, ^{
NSLog(@"下载 A %@", [NSThread currentThread]);
});
// 异步
dispatch_async(queue, ^{
NSLog(@"下载 B %@", [NSThread currentThread]);
});
};
dispatch_async(queue, task);
}
- 主队列调度同步队列不死锁
- (void)gcdDemo3 {
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);
void (^task)() = ^ {
// 同步
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"死?");
});
};
// 异步
dispatch_async(queue, task);
}
主队列在
主线程空闲时
才会调度队列中的任务在主线程执行
代码示例
同步的作用:为了保证我们任务执行的先后顺序
这个在耗时操作里面,又要保证任务的先后顺序的时候,用得最多
1.登录 2.同时下载三部大片
/**
* 触摸事件
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 执行耗时操作
[self execLongTimeOperation];
}
/**
* 执行耗时操作的方法
*/
- (void)execLongTimeOperation {
// 全局队列,异步方法:开启子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 1.当前队列,同步方法:登陆(保证先执行登陆)
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"登陆===%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
});
// 2.同时下载三部电影(不需要先后顺序)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"downLoadA----%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"downLoadV----%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"downLoadI----%@",[NSThread currentThread]);
});
});
}
<延迟操作>
// MARK: - 延迟执行,实现原理
- (void)delay {
/**
从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码
参数
1. when 从现在开始,经过多少纳秒
2. queue 队列
3. block 异步执行的任务
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
void (^task)() = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 主队列
// dispatch_after(when, dispatch_get_main_queue(), task);
// 全局队列
// dispatch_after(when, dispatch_get_global_queue(0, 0), task);
// 串行队列
dispatch_after(when, dispatch_queue_create("lishidao", NULL), task);
NSLog(@"come here");
}
- (void)after {
[self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];
NSLog(@"come here");
}
代码示例
- 执行过程中,界面的segment是可以交互的,没有阻塞主线程
- dispatch_after是异步的,先执行完后面的操作,又回来执行异步方法中操作
- 应用场景: 动画
#pragma mark - 解析延时操作
/**
* 1.封装执行延时操作的方法
* 2.在viewDidLoad方法中调用方法
* 3.输出结果,打印顺序
> ----end
> 下载完毕---{number = 1, name = main}
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 执行延时操作
[self execDispatchAfter];
}
/**
* 执行延时操作的方法
*/
- (void)execDispatchAfter {
/**
参数1.延时多少纳秒,整个延迟3秒
参数2:是决定,参数在哪个线程里面调用
参数3:任务执行的代码块
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"下载完毕---%@", [NSThread currentThread]);
});
NSLog(@"----end");
}
<一次性执行>
一. 经典场景
有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”
// MARK: 一次性执行
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"一次性吗?");
});
NSLog(@"come here");
}
- dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的
- 以下代码用于测试多线程的一次性执行
- (void)demoOnce {
for (int i = 0; i < 10; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self once];
});
}
}
二. 单例测试
1. 单例的特点
- 在内存中只有一个实例
- 提供一个全局的访问点
2. 单例实现
// 使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
面试时只要实现上面
sharedSingleton
方法即可
<调度组>
一. 常规用法
- (void)group1 {
// 1. 调度组
dispatch_group_t group = dispatch_group_create();
// 2. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 3. 将任务添加到队列和调度组
dispatch_group_async(group, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务 1 %@", [NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"任务 2 %@", [NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"任务 3 %@", [NSThread currentThread]);
});
// 4. 监听所有任务完成
dispatch_group_notify(group, q, ^{
NSLog(@"OVER %@", [NSThread currentThread]);
});
// 5. 判断异步
NSLog(@"come here");
}
二. 代码示例
- 调度组监听的实现原理:
每一个任务加入调度组时计数器+1
每完成一个任务调度组内计数器-1
技术器为0时,在参数2的线程中执行参数的操作
#pragma mark - 演示调度组
/**
* 1.封装实现调度的组的方法
* 2.在触摸开始方法中调用
*/
/**
* 触摸开始时执行调度组
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 执行调度组
[self execGroupDispatch];
}
/**
* 执行调度组的方法
*/
- (void)execGroupDispatch {
// 1.创建调度组
dispatch_group_t group = dispatch_group_create();
// 2.获取全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
// 3.三个下载任务
void (^task1) () = ^(){
NSLog(@"%@--下载片头", [NSThread currentThread]);
};
void (^task2) () = ^(){
NSLog(@"%@--下载内容", [NSThread currentThread]);
};
void (^task3) () = ^(){
NSLog(@"%@--下载片尾", [NSThread currentThread]);
};
// 4.将队列和任务添加到调度组
dispatch_group_async(group, globalQueue, task1);
dispatch_group_async(group, globalQueue, task2);
dispatch_group_async(group, globalQueue, task3);
// 5.监听函数
/**
参数1:组
参数2:参数3在哪个线程里面执行
参数3:组内完全下载完毕之后,需要执行的代码
*/
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//表示组内的所有任务都完成之后,会来到这里
NSLog(@"把下好的视频按照顺序拼接好,然后显示在UI去播放");
});
}