GCD使用的总结

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中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

有不对的地方,多指正。。。谢谢!!

你可能感兴趣的:(GCD使用的总结)