在我们做iOS开发的过程中,经常会与多线程打交道,异步绘制,网络请求等,方式有NSThread,NSOperationQueue,GCD等,在这里GCD的地位举足轻重,那么今天写一篇关于GCD的文章。首先迎来第一个问题:
什么是GCD
全名叫 Grand Central Dispatch 是一种异步执行任务的技术,一套基于c语言实现的api,语法十分简洁,只需要简单定义任务或按需加入到队列中,就可以按照计划实现功能,下面一个简单的例子。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//网络请求
//异步绘制图像
//数据库访问等
dispatch_async(dispatch_get_main_queue(), ^{
//刷新UI
});
});
在这里使用异步的方式定义了一个任务,并将其添加到一个全局队列中,这里面的任务内容可以进行一些耗时操作,由于是异步所以不影响主线程,当任务结束之后,同样使用异步的方式定义了一个任务,将其添加到了主队列中,这里执行的任务会在主线程完成。
同步和异步
前面内容提到了“异步”一词,那么这里聊一下什么是“异步”,相应的还有“同步”一词。
异步的作用是不需要CPU立刻响应,而是等待一个信号或回调来下一步操作,当前线程可以立刻执行后续内容,而同步为可以阻塞当前线程,当执行完一个任务之后才能执行下一个。
异步是目的,多线程是手段
当我们开启一个新的线程之后,可以将任务交由新的线程执行,实现异步效果,当需要的时候再返回之前的线程。
用图来做一个同步和异步的解释
异步和同步
这里thread1模拟主线程,当执行任务A的时候由于耗时比较短,所以可以在主线程上完成,当执行到任务B的时候,由于耗时长,所以开辟了一条新的线程,将任务交由子线程处理,同时继续完成任务C,当耗时操作完成之后,将结果返回给主线程进行操作,实现了异步功能。
使用多线程仍然要注意几个问题,1.数据竞争 2.死锁 3.线程开辟太多导致内存消耗过大,不过只要使用得当,多线程对我们开发的好处十分巨大。
串行队列和并发队列
首先解释一下“队列”,如其名字,它是执行处理的等待队列,这里的队列调度方式为先进先出模式(FIFO-First In First Out),也就是先添加到队列中的任务先执行,当然还有其他几种模式,不过这里暂时不考虑,有兴趣的请自行查资料
队列的种类也是两种,Serial Dispatch Queue和 Concurrent Dispatch Queue,分别为串行队列和并发队列,串行队列中的任务必须按照顺序依次执行,并发队列可以多个线程以并发的形式执行。
串行队列和并发队列
下面用代码来看串行队列和并发队列的区别,首先使用串行队列的方式创建几个异步操作
dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"%@---0", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@---1", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@---2", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@---3", [NSThread currentThread]);
});
看看输出结果
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---0
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---1
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---2
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---3
很明显,这里是同一个线程,而且也确实是按照顺序执行的,那么接下来使用并发队列来操作。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"%@---0", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@---1", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@---2", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"%@---3", [NSThread currentThread]);
});
再看看输出结果
<NSThread: 0x60400046adc0>{number = 5, name = (null)}---2
<NSThread: 0x60400046b000>{number = 3, name = (null)}---0
<NSThread: 0x6000004655c0>{number = 4, name = (null)}---1
<NSThread: 0x6000004653c0>{number = 6, name = (null)}---3
很明显,并没有按照顺序执行,而且不是同一个线程,这就印证了上面所说的串行队列和并发队列的区别。
但是不代表创建了多少个并发任务就会开辟多少个线程,这个线程数量由XNU内核来决定,将上面的案例调整为8个任务,可以看到有些线程是重复的,表明了如果一开始就可以开辟8个线程,就不会出现这种情况,所以当一个任务结束之后,下一个任务可以放到完成的线程中,但是和串行队列还是有区别的
多个任务的并发队列
首先会开辟4条线程处理任务,将task0-task3异步形式放入线程中执行,假定task0首先完成,并发队列中取出task4放入线程3中执行,以此类推,所以也就造成了有些线程是重复的原因。
DISPATCH MAIN QUEUE/DISPATCH GLOBAL QUEUE
我们平时常用的大多是这两种,前者是在主线程执行的queue,由于主线程只有一个,自然是个串行队列,而global_queue是个并发队列,这个队列有4个优先级,分别是低优先级(DISPATCH_QUEUE_PRIORITY_LOW),默认优先级(DISPATCH_QUEUE_PRIORITY_DEFAULT),高优先级(DISPATCH_QUEUE_PRIORITY_HIGH),和后台优先级(
DISPATCH_QUEUE_PRIORITY_BACKGROUND),代码如下
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
GCD的api
前面已经提到一些关于GCD的api,下面会详细的探讨这些api的功能
dispatch_queue_create
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.example.gcdDeo",NULL);
这里是创建一个queue的方法,返回类型为dispatch_queue_t,方法含有两个参数,一个是队列名,推荐使用应用程序ID逆序的域名方式,后面参数表示生成的队列类型,一种为DISPATCH_QUEUE_SERIAL为串行队列,这里也可以使用NULL,这个宏定义的值就是NULL,另一种是DISPATCH_QUEUE_CONCURRENT表示并发队列,这里虽然串行队列中的任务是按顺序执行的,但是如果创建多个串行队列,是会并行处理。
并行执行的多个Serial Dispatch Queue
由于一个队列只操作一个线程,所以在执行一些比较重要的操作时,尽量使用串行队列,避免造成数据冲突。
dispatch_(a)sync
最常用的就是这两个函数了dispatch_sync同步执行,dispatch_async异步执行,第一个参数为指定的队列名,block为执行的任务内容,但是使用gcd也要注意一些问题
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
1
//接下来报错
同步主队列的问题
看得出这个问题的原因,同步的方式在主线程执行一个任务,造成了线程阻塞。
dispatch_after
当我们需要在一段时间之后延迟执行某些任务的时候,可以使用dispatch_after这个函数,示例代码如下:
NSLog(@"延迟之前");
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"延迟3秒执行");
});
这里的返回结果为
2018-09-04 14:52:59.437518+0800 GCDDemo[75192:21501573] 延迟之前
2018-09-04 14:53:02.438042+0800 GCDDemo[75192:21501573] 延迟3秒执行
可以看得出的确是延迟了3秒执行的任务,不过这里的实际意义为在3秒后将任务添加到主队列中执行,由于Main Dispatch Queue在主线程的Runloop中,所以真实的时间为3秒加上最短立刻最长Runloop一次循环的时间,并且如果Runloop中堆积的任务较多,可能时间会更长,所以时间并不是完全精确,不过想要大致完成延迟功能,dispatch_after是完全没有问题的。
time为dispatch_time_t类型,从第一个参数时间开始,到第二个指定后的时间结束,这里NSEC_PER_SEC是时间类型,以秒为单位,NSEC_PER_MSEC是以毫秒做单位,其他类型单位这里不多解释了。
dispatch_group
项目中我们经常会遇到将多个异步任务的结果同时返回,如果只是同步的话,执行完最后一个任务就是结束,但是异步却不行,所以这里我们需要使用dispatch_group,代码如下
NSLog(@"全部开始-----%@", [NSThread currentThread]);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
sleep(4);
NSLog(@"子线程1-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"子线程2-----%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部结束-----%@", [NSThread currentThread]);
});
这里使用sleep模拟了两个耗时操作,并且打印了几个位置所处的线程,首先使用dispatch_group_create创建了一个组,并且使用了global queue,接下来模拟异步延时操作,使用dispatch_group_async这个函数和dispatch_async的功能是一样的,只不过前者归属于第一个参数这个组,当函数全部结束之后会调用dispatch_group_notify这个方法,表示所有异步操作都已经结束,代码执行结果如下
2018-09-04 16:22:03.449143+0800 GCDDemo[76086:21787405] 全部开始-----<NSThread: 0x6040002601c0>{number = 1, name = main}
2018-09-04 16:22:06.451174+0800 GCDDemo[76086:21787469] 子线程2-----<NSThread: 0x60400027ed40>{number = 3, name = (null)}
2018-09-04 16:22:07.452564+0800 GCDDemo[76086:21787470] 子线程1-----<NSThread: 0x600000470100>{number = 4, name = (null)}
2018-09-04 16:22:07.452926+0800 GCDDemo[76086:21787405] 全部结束-----<NSThread: 0x6040002601c0>{number = 1, name = main}
这里可以看出的确是当两个子线程都完成之后,才回到主线程的回调中,另外一种方式通过dispatch_group_wait方式阻止后续操作,直到异步函数全部完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"全部结束-----%@", [NSThread currentThread]);
这里wait表示等待,第一个参数表示等待的对象,类型是dispatch_group_t,第二个参数表示等待时间,这里使用DISPATCH_TIME_FOREVER表示一直等待,所以这个函数变成了直到组中代码全部结束,等待才会停止,不过这里还是更加推荐使用dispatch_group_notify方式,因为dispatch_group_wait是同步的,所以不推荐在主线程中使用。
dispatch_group_enter/dispatch_group_leave
实际工作中,会出现让多个请求同时返回结果的案例,这时如果用上面的方法会造成一定问题,因为上面异步的任务只是一个NSLog,如果是一个延时请求呢,下面模拟一下多个请求同时发生的案例。
NSLog(@"全部开始-----%@", [NSThread currentThread]);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(4);
NSLog(@"模拟请求1-----%@", [NSThread currentThread]);
});
});
dispatch_group_async(group, queue, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(3);
NSLog(@"模拟请求2-----%@", [NSThread currentThread]);
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部结束-----%@", [NSThread currentThread]);
});
这里在dispatch_group_async这些个异步任务中,再次用异步的方式模拟了一个请求任务,下面看一下结果
2018-09-04 16:50:50.221045+0800 GCDDemo[76421:21817204] 全部开始-----<NSThread: 0x60000007ecc0>{number = 1, name = main}
2018-09-04 16:50:50.249321+0800 GCDDemo[76421:21817204] 全部结束-----<NSThread: 0x60000007ecc0>{number = 1, name = main}
2018-09-04 16:50:53.225810+0800 GCDDemo[76421:21817394] 模拟请求2-----<NSThread: 0x604000663000>{number = 5, name = (null)}
2018-09-04 16:50:54.225917+0800 GCDDemo[76421:21817393] 模拟请求1-----<NSThread: 0x6040004649c0>{number = 3, name = (null)}
造成这样的原因是发起请求的两个任务已经完成了,所以调用了notify方法,但是请求的结果还没有成功,所以这样的代码是有问题的,那么这里就使用dispatch_group_enter和dispatch_group_leave这对方法来解决。
NSLog(@"全部开始-----%@", [NSThread currentThread]);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(4);
NSLog(@"模拟请求1-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
});
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(3);
NSLog(@"模拟请求2-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部结束-----%@", [NSThread currentThread]);
});
这样的结果就没有问题了,这对方法可以理解为retain和release,当每次进入到一个新的异步任务中时,使用dispatch_group_enter告诉原本的异步任务这里还没有结束,当请求完成之后使用dispatch_group_leave表示已经结束了,可以退出这个异步操作了,这样的话,才能保证真正完成了一个延时函数,结果如下
2018-09-04 16:54:22.832901+0800 GCDDemo[76462:21821447] 全部开始-----<NSThread: 0x60000007e980>{number = 1, name = main}
2018-09-04 16:54:25.837875+0800 GCDDemo[76462:21821502] 模拟请求2-----<NSThread: 0x604000460840>{number = 3, name = (null)}
2018-09-04 16:54:26.833634+0800 GCDDemo[76462:21821503] 模拟请求1-----<NSThread: 0x6000004676c0>{number = 4, name = (null)}
2018-09-04 16:54:26.833992+0800 GCDDemo[76462:21821447] 全部结束-----<NSThread: 0x60000007e980>{number = 1, name = main}
当前案例还有其他的解决方案,我准备在下一篇文章中将案例的其他解决办法写出来,这里不加赘述了。
dispatch_barrier_(a)sync
再次通过一个案例来认识这个函数,假设有a,b,c,d四个异步任务,我新增加了一个任务new,我希望可以在a和b之后完成并且在c和d之前完成,这里可以通过同步等其他方式解决,不过有个更好的方式解决这个问题,dispatch_barrier_async,和它的名字一样,可以理解为一个栅栏,将前后分隔开,在下先上代码
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"task - A");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task - B");
});
NSLog(@"before barrier");
dispatch_barrier_sync(queue, ^{
NSLog(@"task - new");
});
NSLog(@"after barrier");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task - C");
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"task - D");
});
这里模拟了四个异步操作任务,将中间位置插入了一个new任务,这里看看返回结果
2018-09-04 17:55:08.783989+0800 GCDDemo[76920:21886183] before barrier
2018-09-04 17:55:10.787647+0800 GCDDemo[76920:21886310] task - B
2018-09-04 17:55:11.785806+0800 GCDDemo[76920:21886309] task - A
2018-09-04 17:55:11.786003+0800 GCDDemo[76920:21886183] task - new
2018-09-04 17:55:11.786164+0800 GCDDemo[76920:21886183] after barrier
2018-09-04 17:55:12.790452+0800 GCDDemo[76920:21886312] task - D
2018-09-04 17:55:13.790432+0800 GCDDemo[76920:21886309] task - C
这里可以看出,由于有模拟延时操作,所以before首先输出,然后执行了A和B两个任务,而barrier中的任务并没有延时却在A和B之后,说明栅栏功能生效,有因为是sync同步的关系,所以后面的after紧接着输出,这里把sync换成async会有什么结果呢
2018-09-04 17:59:29.114406+0800 BlockDemo[76948:21890748] before barrier
2018-09-04 17:59:29.114600+0800 BlockDemo[76948:21890748] after barrier
before和after是紧挨着输出的,说明dispatch_barrier_sync同样有同步的作用,而dispatch_barrier_async也有着异步的效果。
dispatch_apply
如果有一个案例需要按指定次数执行gcd的任务,可以用这个函数,第一个参数为次数,第二个参数为指定的队列,第三个参数是带参数的block,参数为当前次数下标
NSArray * array = @[@"1",@"2",@"3",@"4",@"5",];
dispatch_apply([array count], dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"元素:%@----第%ld次", array[index],index);
});
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942336] 元素:4----第3次
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942205] 元素:1----第0次
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942337] 元素:3----第2次
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942335] 元素:2----第1次
2018-09-04 18:44:56.970380+0800 GCDDemo[77406:21942205] 元素:5----第4次
dispatch_semaphore
前文已经有讲部分关于产生不一样数据的处理问题,不过有时需要更细致的排他控制,例如你到了一个屋子,里面有一个椅子,你可以坐下,如果再拿来一个椅子,依然可以坐下,但是如果把椅子拿走,那么只能站着等待了,semaphore使用信号量的方式实现了类似的情况,首先第一个函数:
dispatch_semaphore_create(0);
这里创建了一个semaphore,返回类型是dispatch_semaphore_t,有一个参数,表示初始信号量的值,这里给0,如果遇到wait则需要等待。
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);
当第一个函数中的信号量0时,这个函数需要进行等待,如果大于0,则将信号量-1,第二个参数为等待的时间,这个可以根据需求使用,这里假定需要等待无限长的时间,知道信号量增加未知
第二个函数中会给信号量+1,可以看出这两个函数是要成对出现的,如果单独出现wait而且初始化为0的话,会造成后续任务全部卡住无法执行。下面给一个小的案例
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_async(group, queue, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(3);
NSLog(@"完成1");
dispatch_semaphore_signal(semaphore);
});
});
dispatch_group_async(group, queue, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
NSLog(@"完成2");
dispatch_semaphore_signal(semaphore);
});
});
dispatch_group_notify(group, queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"全部完成");
});
这里再次模拟了多个请求需要同时返回结果的问题,通过信号量的方式,使用信号量的方式同样可以完成这个需求,当notify中有两个等待信号的时候,只能通过请求成功的信号量增加的方法去抵消,当两个请求全部完成的时候,等待信号也全部结束,这时表示任务全部完成。
不过如果更换一下需求会怎么样呢,如果我们需要让多个请求同步执行要怎么做,首先我们需要开启异步去管理,同样请求也是异步方法,所以我们用这种方式不能让请求同步执行,这里需要使用线程依赖的方式操作,GCD线程依赖这里面也使用semaphore的方式去做,修改代码如下
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore0 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(0);
dispatch_group_async(group, queue, ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"完成1");
dispatch_semaphore_signal(semaphore0);
});
});
dispatch_group_async(group, queue, ^{
dispatch_semaphore_wait(semaphore0,DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"完成2");
dispatch_semaphore_signal(semaphore1);
});
});
dispatch_group_notify(group, queue, ^{
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"全部完成");
});
这里创建了两个信号量,因为任务1模拟了时间更久,我们这里需要让任务1先完成,那么我们在任务2中把任务1的信号量等待,直到任务1完成并增加信号量,再执行任务2,结果如下
2018-09-05 10:27:02.725024+0800 GCDDemo[81615:22654482] 完成1
2018-09-05 10:27:04.727478+0800 GCDDemo[81615:22654482] 完成2
2018-09-05 10:27:04.727852+0800 GCDDemo[81615:22654485] 全部完成
可以看出我们的目的已经实现了,但是如果有多个请求任务呢,必然需要创建多个信号量,按照需要的顺序进行依赖,但是这种方法其实写起来容易乱,最好的方式是使用NSOperationQueue添加线程依赖,但是这里不多加赘述,同样后续文章中会详细分析一次此案例,并使用其他方式解决这个问题。
dispatch_once
这个函数应该也不会陌生,当我们希望只会执行一次的函数我们会使用dispatch_once,所有我们创建单例的时候也会使用到这个函数。
+ (instancetype)shareInstance {
static Manager * manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [Manager new];
});
return manager;
}
这种单例不需要担心线程问题,即使是多线程环境下也一定是安全的,onceToken会保证运行过程中这部分只会执行一次。
GCD的实现
GCD的使用是十分方便的,这里探讨一下它是如何实现的
C语言实现的用于管理追加Block的FIFO队列
用于排他控制的atomic类型的轻量级信号
C语言实现的管理线程的容器
首先确定一下用于实现dispatch queue的软件组件
我们所使用的全部api都处于libdispatch库中,Dispatch Queue通过结构体和链表实现FIFO队列。FIFO通过dispatch_async等函数管理添加的block。
但是block并不是直接加入到FIFO队列中,而是先加入Dispatch Continuation这个dispatch_continuation_t类型的结构体中,再加入到队列中,这个结构体包含了block所属的group等信息,也就是执行上下文。
XNU内核有4中workqueue,优先级和Global Dispatch Queue的优先级相同,当在Global Dispatch Queue中执行block时,libdispatch从FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数,将自身信息,符合其优先级的workqueue信息以及执行回调函数等传递给参数。
pthread_work_queue_additem_np函数使用workq_kernreturn系统调用。通知workqueue应当执行的项目。根据通知,XNU内核基于系统判断是否生成线程,如果是overcommit属性则始终生成线程。
workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数,在回调函数中执行加入到Dispatch Continuation的Block。
Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理。开始准备执行Global Dispatch Queue的下一个Block
以上就是Dispatch Queue的大致执行过程。
Dispatch Source
GCD中除了常用的Dispatch Queue外,还有Dispatch Source,它具有很多种类型处理能力,最常见的就是使用定时器。
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//创建一个dispatch_source_t类型变量,类型指定为定时器
dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1.0*NSEC_PER_SEC, 0);
//指定定时器执行时间为每秒执行一次
dispatch_source_set_event_handler(timer, ^{
NSLog(@"定时内容");
//每秒执行的内容
});
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"定时取消");
//取消定时器的回调
});
dispatch_resume(timer);
//启动定时器
//dispatch_source_cancel(timer);
//取消定时器
这里本身会存在一个问题,就是set_event_handle这个回调会不执行,原因是当执行过作用域之后,这个source可能会被释放掉,所以可以使用添加到属性的方式放大source的作用域,保证定时器可以始终执行
@property (nonatomic, strong) dispatch_source_t timer;
这里如果内存管理语义使用了assign创建定时器,则会报出会被释放的错误。
使用Dispatch Queue本身是不具备“取消”功能的,要么放弃取消,要么使用NSOperationQueue等方法,而Dispatch Source具备该功能,而且取消后执行的处理可以使用block形式,这里也能看出Dispatch Source的强大功能。
到此为止关于GCD的这篇文章就结束了,如果文章中出现问题欢迎指出,并且如果有更优秀的看法也欢迎提出或讨论。
本文部分内容参考自《Objective-C高级编程》一书,有兴趣的小伙伴可以翻看一下
网友评论