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,建议大家多看看官方文档
暂时就这么多,等有时间再更新更多的用法。