GCD基础篇

GCD全称是Grand Central Dispatch,大中央调度,是系统级的线程管理。
GCD源码
首先附上GCD官方文档
官方如是介绍:
Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.
翻译:通过向系统管理的调度队列添加任务,在多核硬件上同时执行代码。
GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.
翻译: GCD运行在系统级,可以更好地满足所有正在运行的应用程序的需求,以平衡的方式将它们与可用的系统资源进行匹配。

一句话介绍:GCD是系统级的线程管理(执行任务的性能非常高),会自动利用CPU内核(多核),GCD会自动管理线程的周期--创建 调度 销毁。

很厉害有木有。做了一些我们在应用级别做不到事。GCD是C语言的,封装了很多实用的API,下面我们就一起来学习下。

一、队列与任务

  • 队列:是一种常用的数据结构,遵从FIFO的原则
  • 串行队列:顺序执行(一个任务完成了,才能从队列里面取出下一个任务)
  • 并发队列:同时执行很多个任务(可以同时取出很多个任务,只要有线程去执行)
  • 同步任务:sync 优先级高,在线程中有执行顺序,不会开启新的线程
  • 异步任务:async优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程
  • 主队列:dispatch_get_main_queue()系统提供的全局可用的串行队列,可以添加异步任务,不能添加同步任务
  • 全局队列: 系统提供的全局可用的并发队列,根据优先级不同分为四种并发队列

Important : 给主队列添加同步任务会造成死锁

1、全局并发队列

dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)

identifier
为此队列执行的任务指定的服务质量。 服务质量有助于确定队列执行任务的优先级,服务质量高<=>优先级高
QoS:quality of service
flags: 预留参数,0即可

Dispatch Queue Priorities GCD QoS classes (defined in sys/qos.h)
DISPATCH_QUEUE_PRIORITY_HIGH QOS_CLASS_USER_INITIATED
DISPATCH_QUEUE_PRIORITY_DEFAULT QOS_CLASS_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW QOS_CLASS_UTILITY
DISPATCH_QUEUE_PRIORITY_BACKGROUND QOS_CLASS_BACKGROUND
GCD QoS classes (defined in sys/qos.h) Corresponding Foundation QoS classes
QOS_CLASS_USER_INTERACTIVE NSQualityOfServiceUserInteractive
QOS_CLASS_USER_INITIATED NSQualityOfServiceUserInitiated
QOS_CLASS_DEFAULT NSQualityOfServiceDefault
QOS_CLASS_UTILITY NSQualityOfServiceUtility
QOS_CLASS_BACKGROUND NSQualityOfServiceBackground
Global queue Corresponding QoS class
Main thread User-interactive
DISPATCH_QUEUE_PRIORITY_HIGH User-initiated
DISPATCH_QUEUE_PRIORITY_DEFAULT Default
DISPATCH_QUEUE_PRIORITY_LOW Utility
DISPATCH_QUEUE_PRIORITY_BACKGROUND Background
  • QOS_CLASS_USER_INTERACTIVE:User-interactive 与用户交互的工作,例如在主线程上操作,刷新用户界面,或执行动画。如果工作不迅速发生,用户界面可能会出现冻结。关注响应性和性能。主线程在这个级别工作。
  • QOS_CLASS_USER_INITIATED:User-initiated 用于执行用户触发的,并且需要立即获得结果,以便进一步进行用户交互的任务。 例如,例如打开保存的文档或者邮件列表选中之后需要加载电子邮件。
  • QOS_CLASS_UTILITY:可能需要一些时间才能完成的工作,不需要立即的结果。实用任务通常有一个对用户可见的进度条。专注于在响应能力、性能和能源效率之间提供平衡。 例如,定期进行内容更新或批量文件操作,如媒体导入。
  • QOS_CLASS_BACKGROUND:在后台运行的工作,对用户来说是不可见的,关注能源效率。比如索引、同步和备份。
  • QOS_CLASS_DEFAULT:这个QoS的优先级介于User-interactive和User-initiated之间。这个QoS不打算被开发人员用来对工作进行分类。未分配QoS信息的工作被视为默认值,GCD全局队列在这个级别上运行。

【小贴士】 NSOperation的默认QoS是NSQualityOfServiceBackground

【优先级反转】
特别注意的是,任务之间关系复杂时,优先级尽量简单点,不然会造成优先级反转,危害很大!
由于优先级反转,直接就导致任务错乱,逻辑错乱。此外造成任务调度时,时间的不确定性。破坏了实时系统的实时性,严重时可能导致系统崩溃。

那什么是优先级反转呢?

当高优先级的工作依赖于较低优先级的工作时,或者它成为低优先级工作的结果,则会发生优先级反转。结果,可能会发生阻塞、旋转和轮询。
在同步工作的情况下,系统将通过在反转期间提高低优先级工作的QoS来自动解决优先级反转。这将发生在以下情况:
在串行队列上调用dispatch_sync()和dispatch_wait()时。
当调用pthread_mutex_lock()时,互斥对象被一个带有较低QoS的线程所控制。在这种情况下,持有锁的线程被提高到调用者的QoS。但是,这个QoS升级不会出现在多个锁之间。
在异步工作的情况下,系统将尝试解决串行队列中出现的优先级反转。

优先级解决办法?

Developers should try to ensure that priority inversions don’t occur in the first place, so the system isn’t forced to attempt a resolution.

2、自定义队列

    //创建一个非主线程的实验环境
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"添加任务前--当前线程--%@",[NSThread currentThread]);
        
        //串行队列同步执行
        //创建一个串行队列 第一个参数:给队列起一个名字  第二个参数:队列属性,串行还是并行
        dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
        //添加异步任务
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_async(serialQueue, ^{
                NSLog(@"串行队列异步任务%ld--当前线程--%@",i,[NSThread currentThread]);
            });
        }
        
        //添加同步任务
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_sync(serialQueue, ^{
                NSLog(@"串行队列同步任务%ld--当前线程--%@",i,[NSThread currentThread]);
            });
        }
    });
控制台输出
2017-10-24 10:11:19.243669+0800 GCD[21493:746313] 添加任务前--当前线程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.243890+0800 GCD[21493:746312] 串行队列异步任务0--当前线程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244002+0800 GCD[21493:746312] 串行队列异步任务1--当前线程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244152+0800 GCD[21493:746312] 串行队列异步任务2--当前线程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244313+0800 GCD[21493:746312] 串行队列异步任务3--当前线程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244572+0800 GCD[21493:746312] 串行队列异步任务4--当前线程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.246669+0800 GCD[21493:746313] 串行队列同步任务0--当前线程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.247604+0800 GCD[21493:746313] 串行队列同步任务1--当前线程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.248015+0800 GCD[21493:746313] 串行队列同步任务2--当前线程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.248445+0800 GCD[21493:746313] 串行队列同步任务3--当前线程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.248746+0800 GCD[21493:746313] 串行队列同步任务4--当前线程--<NSThread: 0x600000271e00>{number = 3, name = (null)}

串行队列异步任务:会开辟一个线程,一个一个按照顺序执行任务

串行队列同步任务:不开辟新的线程,在当前线程一个一个按照顺序执行任务

//创建一个非主线程的实验环境
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //创建并行队列
        dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"添加任务前--当前线程--%@",[NSThread currentThread]);
        //添加异步任务
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_async(currentQueue, ^{
                NSLog(@"并行队列异步任务%ld--当前线程--%@",i,[NSThread currentThread]);
            });
        }
        //添加同步任务
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_sync(currentQueue, ^{
                NSLog(@"并行队列同步任务%ld--当前线程--%@",i,[NSThread currentThread]);
            });
        }
    });
2017-10-24 10:36:53.944242+0800 GCD[21646:774599] 添加任务前--当前线程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.944488+0800 GCD[21646:774603] 并行队列异步任务1--当前线程--<NSThread: 0x60000027bcc0>{number = 5, name = (null)}
2017-10-24 10:36:53.944493+0800 GCD[21646:774599] 并行队列同步任务0--当前线程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.944489+0800 GCD[21646:774597] 并行队列异步任务2--当前线程--<NSThread: 0x60400046e740>{number = 4, name = (null)}
2017-10-24 10:36:53.944628+0800 GCD[21646:774603] 并行队列异步任务3--当前线程--<NSThread: 0x60000027bcc0>{number = 5, name = (null)}
2017-10-24 10:36:53.944527+0800 GCD[21646:774596] 并行队列异步任务0--当前线程--<NSThread: 0x60000027c040>{number = 6, name = (null)}
2017-10-24 10:36:53.944635+0800 GCD[21646:774599] 并行队列同步任务1--当前线程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.944703+0800 GCD[21646:774598] 并行队列异步任务4--当前线程--<NSThread: 0x60400046e900>{number = 7, name = (null)}
2017-10-24 10:36:53.945277+0800 GCD[21646:774599] 并行队列同步任务2--当前线程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.945801+0800 GCD[21646:774599] 并行队列同步任务3--当前线程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.946104+0800 GCD[21646:774599] 并行队列同步任务4--当前线程--<NSThread: 0x60000007b580>{number = 3, name = (null)}

并行队列异步任务:开辟多个线程,并发执行,执行没有顺序

并行队列同步任务:不开辟线程,在当前线程按照顺序一个个执行

    /*给自定义队列指定优先级
     *attr: 队列属性,串或并
     *qos_class:QoS
     *relative_priority:必须是一个小于0大于QOS_MIN_RELATIVE_PRIORITY(-15)的数
     */
    dispatch_queue_attr_t att = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("seiialqueue2", att);
    dispatch_async(serialQueue2, ^{
        //添加需要执行的任务
    });

二、实用API

1、dispatch_once
一个application生命周期内block内只执行一次。
【应用场景】:创建单例

#import "Test8.h"

@implementation Test8

+ (Test8 *)shareInstance{

    static Test8 * test8 = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        test8 = [[Test8 alloc] init];
    });
    return test8;
}

@end

2、dispatch_time
用于创建一个相对于默认时钟创建dispatch_time_t或修改现有的dispatch_function_t。
默认时钟:是基于mach_absolute_time的。
【应用场景】配合其他API实现定时、延时操作等。

dispatch_time_t time = dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)

第一个参数:when

 DISPATCH_TIME_NOW//当前时间
 DISPATCH_TIME_FOREVER//一个很遥远的时间 
 通常可以用来控制定时器是否开始

第二个参数:delta
在时间参数when基础上添加的纳秒数。

1秒(s)=1000000000纳秒(ns)
1毫秒(ms)=1000000纳秒(ns)
1秒(s)=1000000微秒(μs)
1微秒(μs)=1000纳秒(ns)
#define NSEC_PER_SEC 1000000000ull  //每秒有多少纳秒
#define NSEC_PER_MSEC 1000000ull    //每毫秒有多少纳秒
#define USEC_PER_SEC 1000000ull     //每秒有多少微妙
#define NSEC_PER_USEC 1000ull       //每微秒有多少纳秒

所以一秒钟通常可以这么表示

1*NSEC_PER_SEC
1000*NSEC_PER_MSEC
NSEC_PER_USEC*NSEC_PER_MSEC
NSEC_PER_USEC* USEC_PER_SEC

3、dispatch_after 延时操作
此函数等待直到指定的时间,然后异步地将block添加到指定的队列。而不是立即执行。
【应用场景】页面加载完成之后,弹出广告

dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
 //创建一个时间
 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
 dispatch_after(time, dispatch_get_main_queue(), ^{
    //等待time秒 ,block会被异步提交到主队列
        NSLog(@"2-1");
 });

传递DISPATCH_TIME_NOW作为when参数被支持,但不如调用dispatch_async最佳。 传递DISPATCH_TIME_FOREVER未定义。

 dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
        NSLog(@"2-1");
    });

4、dispatch_barrier_async
barrier提交的block会等待barrier之前提交的任务全部完成,barrier任务执行期间只有它自己执行,后加入的任务等barrier任务执行结束才会执行。
【注意】dispatch_barrier_async只在自己创建的并发队列上起作用,在全局并发队列和串行队列上,效果和dispatch_sync一样。
【应用场景】避免资源竞争 例如 :对同一个文件进行读写操作,其中写操作可以通过dispatch_barrier_async提交。

    dispatch_queue_t currentQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(currentQueue, ^{
            NSLog(@"前置任务%ld",i);
        });
    }
    dispatch_barrier_async(currentQueue, ^{
        NSLog(@"barrier任务");
    });
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(currentQueue, ^{
            NSLog(@"后置任务%ld",i);
        });
    }

控制台输出

2017-10-26 13:34:35.632559+0800 GCD[30827:2384918] 前置任务1
2017-10-26 13:34:35.632559+0800 GCD[30827:2384912] 前置任务0
2017-10-26 13:34:35.632560+0800 GCD[30827:2384915] 前置任务3
2017-10-26 13:34:35.632771+0800 GCD[30827:2384918] 前置任务4
2017-10-26 13:34:35.632604+0800 GCD[30827:2384913] 前置任务2
2017-10-26 13:34:35.634816+0800 GCD[30827:2384913] barrier任务
2017-10-26 13:34:35.635281+0800 GCD[30827:2384918] 后置任务0
2017-10-26 13:34:35.635285+0800 GCD[30827:2384912] 后置任务3
2017-10-26 13:34:35.635287+0800 GCD[30827:2384915] 后置任务2
2017-10-26 13:34:35.635302+0800 GCD[30827:2384913] 后置任务1
2017-10-26 13:34:35.635637+0800 GCD[30827:2384914] 后置任务4

5、dispatch_group_async
dispatch groups可以监听多个异步任务,即使它们可能在不同的队列上运行。完成之后可以收到通知。
dispatch_group_notify :异步执行闭包,不阻塞线程。 group 监听的任务全部完成之后,会调用notify的block
dispatch_group_wait:阻塞式的,会等待之前监听的任务全部完成,成功返回0。
不论哪种方式,成功后group清空,可以继续添加任务。
【应用场景】我们公司的APP是混合模式的,应用启动会根据资源列表下载有变化的资源文件,下载完成之后需要给服务器反馈下载结果。
两种方式的差别如下。

    //创建三个并发队列
    dispatch_queue_t current1 = dispatch_queue_create("current1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t current2 = dispatch_queue_create("current2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t current3 = dispatch_queue_create("current3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    //下载图片
    dispatch_group_async(group, current1, ^{
        NSLog(@"group任务下载图片1---currentThread%@",[NSThread currentThread]);
    });
    //下载图片
    dispatch_group_async(group, current2, ^{
        NSLog(@"group任务下载图片2---currentThread%@",[NSThread currentThread]);
    });
    //下载图片
    dispatch_group_async(group, current3, ^{
        NSLog(@"group任务下载图片3---currentThread%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"图片下载完成,主线程展示");
    });
  
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//    NSLog(@"图片下载完成,主线程展示");

    //再监听一个任务
    /**dispatch_group_enter
     *可以让应用程序通过除了使用dispatch_group_async功能之外的方式显式添加和删除组中的任务,
     *从而正确地管理任务引用计数。 调用此函数必须与调用dispatch_group_leave进行平衡。
     *可以使用此功能将块与多个组同时关联。
     */
    dispatch_async(current2, ^{
        //表示已进入组,是区别于dispatch_group_async的另一种加入组的方式
        dispatch_group_enter(group);
        NSLog(@"group任务下载图片4---currentThread%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

控制台

2017-10-26 17:21:12.387513+0800 GCD[32025:2599998] group任务下载图片1---currentThread<NSThread: 0x604000272640>{number = 3, name = (null)}
2017-10-26 17:21:12.387619+0800 GCD[32025:2599996] group任务下载图片4---currentThread<NSThread: 0x600000467a00>{number = 6, name = (null)}
2017-10-26 17:21:12.387624+0800 GCD[32025:2600001] group任务下载图片3---currentThread<NSThread: 0x600000467980>{number = 5, name = (null)}
2017-10-26 17:21:12.387649+0800 GCD[32025:2599995] group任务下载图片2---currentThread<NSThread: 0x604000272780>{number = 4, name = (null)}
2017-10-26 17:21:12.394213+0800 GCD[32025:2599907] 图片下载完成,主线程展示

注释掉

  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"图片下载完成,主线程展示");
    });

使用dispatch_group_wait等待执行结束

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"图片下载完成,主线程展示");

控制台结果

2017-10-26 17:25:43.719035+0800 GCD[32080:2606880] group任务下载图片3---currentThread<NSThread: 0x60000027df80>{number = 5, name = (null)}
2017-10-26 17:25:43.719053+0800 GCD[32080:2606878] group任务下载图片1---currentThread<NSThread: 0x604000265140>{number = 3, name = (null)}
2017-10-26 17:25:43.719035+0800 GCD[32080:2606879] group任务下载图片2---currentThread<NSThread: 0x60000027e3c0>{number = 4, name = (null)}
2017-10-26 17:25:43.719265+0800 GCD[32080:2606733] 图片下载完成,主线程展示
2017-10-26 17:25:43.719820+0800 GCD[32080:2606879] group任务下载图片4---currentThread<NSThread: 0x60000027e3c0>{number = 4, name = (null)}

6、dispatch_apply
将一个块提交给调度队列进行多次调用,类似for循环,但是dispatch_apply会等到所有block完成才返回。
官方说:将此函数与并发队列一起使用可以作为一个高效的并行循环。
【应用场景】用于混合应用根据资源列表下载资源,配合dispatch_group_enter使用,实现资源下载成功之后,上传下载结果。

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

iterations:要执行的迭代次数。
queue:调度队列
block:执行任务的block
size_t:typedef typeof (sizeof(int)) size_t;

   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   NSArray * arr = @[@"贝加尔湖畔",@"十点半的地铁",@"当你老了",@"春风十里不如你",@"风吹麦浪",@"父亲的散文诗"];
   // 第二个参数queue,可以是并行队列,推荐DISPATCH_APPLY_AUTO,会自动选择一个适合调用线程的队列。
   dispatch_apply(arr.count, queue, ^(size_t i) {
        NSLog(@"dispatch_apply %zu %@  currentThread %@",i,arr[i],[NSThread currentThread]);
   });

控制台

2017-10-27 10:30:32.752105+0800 GCD[34170:3051371] dispatch_apply 0 贝加尔湖畔  currentThread <NSThread: 0x604000267e00>{number = 3, name = (null)}
2017-10-27 10:30:32.752101+0800 GCD[34170:3051273] dispatch_apply 3 春风十里不如你  currentThread <NSThread: 0x6040000712c0>{number = 1, name = main}
2017-10-27 10:30:32.752105+0800 GCD[34170:3051372] dispatch_apply 2 当你老了  currentThread <NSThread: 0x6000004672c0>{number = 5, name = (null)}
2017-10-27 10:30:32.752107+0800 GCD[34170:3051370] dispatch_apply 1 十点半的地铁  currentThread <NSThread: 0x600000467b80>{number = 4, name = (null)}
2017-10-27 10:30:32.752339+0800 GCD[34170:3051273] dispatch_apply 5 父亲的散文诗  currentThread <NSThread: 0x6040000712c0>{number = 1, name = main}
2017-10-27 10:30:32.752338+0800 GCD[34170:3051371] dispatch_apply 4 风吹麦浪  currentThread <NSThread: 0x604000267e00>{number = 3, name = (null)}

7、dispatch_source_create
创建新的调度资源以监听系统的底层对象,并自动将block提交给调度队列以响应事件。

dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue);

type:dispatch source的类型,type可用的值
handle:根据type不同,值不同
mask:根据type不同,值不同

type 用途 handle的值 mask的值
DISPATCH_SOURCE_TYPE_DATA_ADD 数据相加 0 0
DISPATCH_SOURCE_TYPE_DATA_OR 按位OR合并数据 0 0
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口获取消息 mach_port_t 0
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送消息 mach_port_t Dispatch Source Mach Send Event Flags
DISPATCH_SOURCE_TYPE_PROC 事件进程 pid_t Dispatch Source Process Event Flags.
DISPATCH_SOURCE_TYPE_READ 文件可读 文件描述符(int) 0
DISPATCH_SOURCE_TYPE_SIGNAL 信号进程 信号(int) 0
DISPATCH_SOURCE_TYPE_TIMER 定时器 0 0
DISPATCH_SOURCE_TYPE_VNODE 文件系统的变化 文件描述符(int) Dispatch Source Vnode Event Flags
DISPATCH_SOURCE_TYPE_WRITE 文件可写 文件描述符(int) 0
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 系统内存情况 0 FOO
void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);

可以在同一个调度定时器源对象上多次调用dispatch_source_set_timer方法,根据需要重置定时器源的时间间隔。
启动时间参数还决定了哪个时钟用于定时器。 如果开始时间为DISPATCH_TIME_NOW或使用dispatch_time创建,则定时器基于mach_absolute_time。 否则,如果使用dispatch_walltime创建定时器的开始时间,则定时器基于gettimeofday(3)。
如果定时器源已被取消,则调用此功能不起作用。
start:定时开始的时间
interval:纳秒级别的时间间隔,DISPATCH_TIME_FOREVER表示一次性定时器
leeway:纳秒级别允许的延迟,也就是要求计时器触发的精准程度。
【解释leeway】如果每5秒就要求触发一次,对精度要求高,那么可以传0。如果是一个周期性任务,比如每15分钟查询一下邮件,对时间要求不需要很精确,那么可以传60s,告诉系统60秒的误差是可以接受的。这样系统就可以联合其他任务一起执行,不用频繁的切换线程,从而降低系统功耗,提高系统性能。(线程切换有系统开销)
【注意】对于所有定时器,即使指定了leeway为零,也会有一些延迟。

void dispatch_source_cancel(dispatch_source_t source);

阻止处理事件的handler的继续调用,但不会中断已经在进行中的block任务。当事件完成后,cancellation handler被提交到目标队列。cancellation handler在系统释放了全部底层系统对象(文件描述符或mach端口)的引用之后,才提交给dispatch_source的目标队列。因此,cancellation handler是关闭或释放此类系统对象的方便位置。但是在调用cancellation handler之前添加的关闭文件描述符或是回收mach port 的任务是无效的。

GCD定时器 :


#import "TestViewController.h"

@interface TestViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@property (nonatomic, assign) BOOL isSuspend;//是否是挂起状态
@end

@implementation TestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];

    [self creatTimer];
    typeof(self) weakSelf = self;
    //系统释放了全部底层系统对象的引用之后,block会执行
    dispatch_source_set_cancel_handler(self.timer, ^{
        NSLog(@"定时器销毁了%@",weakSelf.timer);
    });
}


//创建定时器⏲
- (void)creatTimer{
    if (!self.timer) {
        self.isSuspend = NO;
        //定时器
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        //事件处理handler
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"定时任务");
        });
        //启动
        /**第一次建议使用dispatch_activate,因为像queues and sources这样的dispatch对象可能
         *inactive状态被创建
         */
        dispatch_activate(timer);

        //创建一个属性,timer需要强引用,局部变量被销毁之后,定时操作就不起作用了。
        self.timer = timer;
    }
    
}

//启动▶️
- (IBAction)resumeTimer{
    if (self.timer&&self.isSuspend) {
        self.isSuspend = NO;
        //如果suspension count计数器为0,并且是非inactive状态,调用这个方法会触发断言终止进程
        dispatch_resume(self.timer);
        NSLog(@"调用dispatch_resume 唤起线程");
    }
}
//暂停⏸
- (IBAction)suspendTimer{
    if (self.timer) {
        self.isSuspend = YES;
        //挂起的对象不会继续调用任何一个关联的block,但是不会中断正在执行的block
        dispatch_suspend(self.timer);
        NSLog(@"调用dispatch_suspend 挂起线程");
    }
}

//停止⏹
- (IBAction)stopTimer{
    if (self.timer) {
        //EXC_BAD_INSTRUCTION
        //如果是挂起状态 调用dispatch_source_cancel 崩溃
        //dispatch_suspend调用后 timer 是无法被释放的,一般情况下会发生崩溃。
        //这是因为dispatch source release 的时候判断了当前是否是在暂停状态。
        if (self.isSuspend) {
            dispatch_resume(self.timer);
        }
        self.isSuspend = NO;
        dispatch_source_cancel(self.timer);
        self.timer = nil;
        NSLog(@"调用dispatch_source_cancel 销毁资源");
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)dealloc{

    NSLog(@"销毁了");
}

@end

【注意】

  • dispatch_resume与dispatch_suspend必须成对使用,但是没有 API 获取当前是挂起还是执行状态,所以需要自己记录。
  • 如果suspension count计数器为0,并且是非inactive状态,调用dispatch_resume方法会触发断言终止进程

【与NSTimer比较】

  • GCD timer不依赖RunLoop,更准时。
  • [NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(refresh) userInfo:nil repeats:YES]; repeats为YES时,self引用计数+1,因此可能导致VC不释放。
  • NSTimer的创建、销毁必须在统一线程、performSelector的创建与撤销必须在同一个线程操作。
  • 不论哪种定时器,不用的时候一定要销毁。

结束语:GCD还有很多很多使用的API,建议大家多看看官方文档
暂时就这么多,等有时间再更新更多的用法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容