GCD(Grand Central Dispatch)可以说是Mac、iOS开发中的一大“利器”,本文就总结一些有关使用GCD的经验与技巧。
1、首先说道GCD,必须先把单例说说
这一条算是“老生常谈”了,但我认为还是有必要强调一次,毕竟非全局或非static的dispatch_once_t变量在使用时会导致非常不好排查的bug
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//单例代码
});
2、聊聊GCD什么时候开线程,什么时候不开线程,什么时候会死锁!
1、队列
GCD中分为串行队列(DISPATCH_QUEUE_SERIAL)和并发队列(DISPATCH_QUEUE_CONCURRENT);
2、同步执行or异步执行
dispatch_async 异步执行
dispatch_sync 同步执行
下面有一个表,讲的是什么时候开不开线程,看一下!
全局队列 | 串行队列 | 主队列 | |
---|---|---|---|
同步执行(sync) | 没有开启线程,串行执行 | 没有开启线程,串行执行 | 会死锁 |
异步执行(async) | 开启新线程,并发执行 | 开启新线程,并发执行 | 没有开启线程,串行执行 |
3、创建队列
dispatch_queue_create,创建队列用的,它的参数只有两个,原型如下:
dispatch_queue_t dispatch_queue_create ( const char *label, dispatch_queue_attr_t attr );
在网上的大部分教程里(甚至Apple自己的文档里),都是这么创建串行队列的:
dispatch_queue_t queue = dispatch_queue_create("LHQueue", NULL);
看,第二个参数传的是“NULL”。 但是dispatch_queue_attr_t类型是有已经定义好的常量的,所以我认为,为了更加的清晰、严谨,最好如下创建队列:
//串行队列
dispatch_queue_t queue = dispatch_queue_create("LHQueue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue = dispatch_queue_create("LHQueue", DISPATCH_QUEUE_CONCURRENT);
- 全局队列
* 调度任务的时候,多个线程,而且是顺序不固定
* 并发能力好
* 效率高,速度快,费电,费钱
- 串行队列
* 调度任务,只有一条线程,顺序执行每一个任务
* 并发能力差
* 效率差,速度慢,省电,省钱
* 用户并不是什么时候都会希望快的!
4、创建多线程
//串行队列异步执行
dispatch_queue_t queue = dispatch_queue_create("LHQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//开启一条线程,并发执行
});
//并发队列异步执行
dispatch_queue_t queue = dispatch_queue_create("LHQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//开启线程,并发执行
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//同上。。。
});
在开发中,回到主线程也可以用
[self performSelectorOnMainThread:@selector(clearCacheSuccess) withObject:nil waitUntilDone:YES];
});
dispatch_async(dispatch_get_main_queue(), ^{
//主线程
});
5、GCD组的使用(Dispatch Groud)
dispatch_group_create();
dispatch_group_async();
dispatch_group_notify();
dispatch_group_wait();
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后才通知界面说完成。该组中是并发执行的。例如:
- (void)gcdGroup{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1]; NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2]; NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3]; NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});
//打印顺序
2016-07-14 23:33:09.593 GCDDemo[959:49260] group1
2016-07-14 23:33:10.593 GCDDemo[959:49262] group2
2016-07-14 23:33:11.594 GCDDemo[959:49261] group3
2016-07-14 23:33:11.595 GCDDemo[959:49222] updateUi
6、dispatch_after
dispatch_after是延迟提交,不是延迟运行,先看看官方文档的说明:Enqueue a block for execution at the specified time.
Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!。
看看如下代码示例:
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//立即打印一条信息
NSLog(@"Begin add block...");
//提交一个block
dispatch_async(queue, ^{
//Sleep 10秒
[NSThread sleepForTimeInterval:10];
NSLog(@"First block done...");
});
//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"After...");
});
//输出
2016-07-14 20:57:27.122 GCDTest[45633:1812016] Begin add block...
2016-07-14 20:57:37.127 GCDTest[45633:1812041] First block done...
2016-07-14 20:57:37.127 GCDTest[45633:1812041] After...
//从结果也验证了,dispatch_after只是延时提交block,并不是延时后立即执行。
//所以想用dispatch_after精确控制运行状态的朋友可要注意了~
7、死锁
dispatch_sync导致的死锁
涉及到多线程的时候,不可避免的就会有“死锁”这个问题,在使用GCD时,往往一不小心,就可能造成死锁,看看下面的“死锁”例子:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"I am block...");
});
你可能会说,这么低级的错误,我怎么会犯,那么,看看下面的:
- (void)updateUI1 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Update ui 1");
//死锁!
[self updateUI2];
});
}
- (void)updateUI2 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Update ui 2");
});
}
在你不注意的时候,嵌套调用可能就会造成死锁!所以用了段子手的一句话“我的愿望是世界和平”=。=,所以我们还是少用dispatch_sync!!!
8、正确创建dispatch_time_t
用dispatch_after的时候就会用到dispatch_time_t变量,但是如何创建合适的时间呢?答案就是用dispatch_time函数,其原型如下:
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。
那么第二个参数就是真正的延时的具体时间。
这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000000000”=。=,太长了,所以理所当然系统提供了常量,如下:
#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
关键词解释:
NSEC:纳秒。
USEC:微妙。
SEC:秒
PER:每
所以:
NSEC_PER_SEC,每秒有多少纳秒。
USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
NSEC_PER_USEC,每毫秒有多少纳秒。
所以,延时1秒可以写成如下几种:
dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
最后一个“USEC_PER_SEC * NSEC_PER_USEC”,翻译过来就是“每秒的毫秒数乘以每毫秒的纳秒数”,
也就是“每秒的纳秒数”,所以,延时500毫秒之类的,也就不难了吧~
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)
(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(),
^{
//这个就是延迟一秒
});
9、dispatch_suspend/dispatch_resume
当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响; 在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再回复。
//dispatch_suspend函数挂起指定的Dispatch Queue
dispatch_suspend(queue);
//dispatch_resume函数恢复指定的Dispatch Queue
dispatch_resume(queue);
这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。