GCD
2016-03-18 本文已影响55人
CoderJackieYip
有2个核心概念
- 任务:执行什么操作
- 队列:用来存放任务
说明:
- GCD封装在libdispatch库中
- 可以用block存放任务,也可以用函数存放任务。如下:
/* block方式 */ dispatch_asyc(queue, ^{...}); /* function方式 */ void function (void *data) {...}; // &data是function的参数指针 dispatch_asyc_f(queue, &data, function);
使用步骤
- 定制任务、创建队列
// 第一个参数:相当于队列名字 // 第二个参数:队列的属性(类型),有两种类型:并发、串行 // 注:GCD调用Create函数创建出来的变量,不需要使用Release去释放 dispatch_queue_t queue = dispatch_queue_create(@"queueName", DISPATCH_QUEUE_CONCURRENT)``` 2. 将任务添加到队列中(GCD会自动将队列中的任务取出,放到对应线程中执行)
两个执行任务的常用函数
- 同步:只能在当前线程中执行任务,不具备开启新线程的能力
// queue:队列 block:任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 异步:可以在新的线程中执行任务,具备开启新线程的能力,但如果放在主队列里,就不会开线程
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
队列类型
- 并发队列(Concurrent Dispatch Queue)
可以让任务并发(同时)执行(自动开启多个线程同时执行)
并发功能只有在异步(dispatch_async)函数下才有效- 串行队列(Serial Dispatch Queue)
让一个任务接着一个地执行(一个完毕才执行下一个)
容易混淆的术语
同步、异步、并发、串行
- 同步、异步:主要体现能否开启新线程
- 同步:不能开线程
- 异步:能开线程
- 并发、串行:任务的执行方式
- 并发:多任务
- 串行:单任务
注:只有在异步函数里添加并发队列,才会并发执行多个任务。其他情况都是串行执行任务(即使开了新线程)。
并发队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
串行队列
dispatch_get_main_queue();
注:两种卡死的情况
- 当在
主线程
执行同步+串行主队列
任务时,将会相互谦让对方,而使得无法继续执行。如下:-(void)viewDidLoad { [super viewDidLoad]; [self syncMain]; } -(void)syncMain { dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{...}); //执行到这里 无法往下执行 dispatch_sync(queue, ^{...}); }
- 当在
同步+串行主队列
的任务中,嵌套创建同步+串行主队列
任务,两个任务将会相互谦让对方,而使得无法继续执行。如下:
> -(void)viewDidLoad {
> dispatch_queue_t queue = dispatch_get_main_queue;
> dispatch_sync{queue, ^{ //执行到这里 无法往下执行
> dispatch_sync{queue, ^{...}
> }
> }
> }
> }
各种队列的执行效果
并发队列 | 手动创建的串行队列 | 主队列 | |
---|---|---|---|
同步 | - 没有 开启新线程- 串行 执行任务 |
- 没有 开启新线程- 串行 执行任务 |
- 没有 开启新线程- 串行 执行任务 |
异步 | - 有 开启新线程- 并发 执行任务 |
- 有 开启新线程- 串行 执行任务 |
- 没有 开启新线程- 串行 执行任务 |
注:
- 以上粗体是我们使用GCD的主要组合
- 使用sync函数往当前
串行
队列中添加任务,会卡住当前的串行任务 - 如果异步函数的任务A里,创建了一个同步函数的任务B,则在A中,B后面的操作会等待B任务执行完,再执行。如下:
dispatch_async(dispatch_get_global(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"先执行我")});
NSLog(@"再执行我")
})```
###其他常用函数
> - 栅栏函数
```objc
/* 在前面的任务之行结束后它才执行,而且它后面的任务等它执行完之后才执行
注:这个queue不能为全局并发队列,必须是自己创建的并发队列 */
dispatch_barrier_async(queue, ^{...});
```
> - 一次性代码
```objc
/*使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次*/
// 该方法是面向成个程序的一次性代码,除了创建单例,其他情况慎用,因为该方法执行一次之后,就在整个程序运行过程中都不会执行了。(相当于该方法的代码从整个程序的代码中消失了,所以创建单例时,必须用static声明以保存单例对象)
dispatch_once(标记指针, ^{...});
// 使用场景:单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只执行1次,是线程安全的});
- 快速迭代
// 第一个参数:(数量)表示迭代的次数
// block的参数是索引
// 说明:如果放到并发队列,在异步函数中执行时,会并发执行。
dispacht_apply(size_t,队列,^(size_t index){});
- 队列组
把队列放到组里,组里的队列任务执行完后,执行组的notify的任务。
// 创建组
dispatch_group_t group = dispatch_group();
// 注意:是dispatch_group_asyc,不是dispatch_asyc
dispatch_group_asyc(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY, 0) ^{...});
dispatch_gourp_asyc(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY, 0) ^{...});
dispatch_group_notify(group, queue, ^{NSLog(@"执行完上面的queue任务之后,就执行我")});
- GCD定时器(不受RunLoop影响)
@interface JKYTimer
@property (nonatomic, strong) dispatch_source_t timer;
@end
// dispatch_after... 是只定时一次的定时器
// 1. 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 创建定时器(dispatch_source_t 本质是OC对象,所以必须给它加上强引用,才不会在局部方法执行完就消亡)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 3. 设置定时器的各种属性(合适开始任务,每隔多长时间执行一次)
// GCD时间参数:一般是纳秒(1秒 == 10的9次方纳秒)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0);
// 4. 设置回调
dispatch_source_set_event_handler(timer, ^{...});
// 5. 启动定时器:该定时器默认是暂停的,必须手动开启
dispatch_resume(self.timer);
//
//注:
// 1. GCD的时间变量:dispatch_time_t,如下:
dispatch_time_t start = 2.0 * NSEC_PER_SEC;
// 如需在该时间变量上加减时间,如加3秒,不能直接start + 3.0;而应该使用其函数设置。
// 2. GCD的时间设置函数:dispach_time(dispatch_time_t when, int64_t delta);如下:
dispatch_time_t start = 2.0 * NSEC_PER_SEC + dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);