GCD基础

最近在学习有关GCD的知识,学习完了想要做个总结,一是加深印象,方便以后查阅,二是拿出来和大家分享一下,一起讨论,一起进步,最后就是希望对于一些跟我一样的iOS初学者有一些帮助。

Dispatch Queue

Serial Dispatch Queue

serial Dispatch Queue即串行队列 串行队列的特点主要就是它只会创建一条线程工作,即单线程工作,我们的主线程即serial Dispatch Queue。

例如我们把三个代码块blk1,blk2,blk3依次加入一个串行队列,么由于这个串行队列只会创建一条线程,而一条线程同时只能执行一个事件,因此首先执行blk1,blk1执行完成之后再执行blk2,blk2执行完成之后再执行blk3,这样依次执行。

在不想改变执行顺序或者不想同时并行执行多个处理时用串行队列。

  • 串行队列的创建
    使用dispatch_queue_create创建队列
dispatch_queue_t queue = dispatch_queue_create("com.mySerialDispatchQueue", NULL); 

dispatch_queue_created的第一个参数是我们创建的队列的标识,第二个参数,如果传入的是NULL,那么就是创建串行队列,如果传入的是DISPATCH_QUEUE_CONCURRENT那么创建的就是并行队列。

  • 下面我们用代码测试一下串行队列
dispatch_queue_t queue = dispatch_queue_create("com.mySerialDispatchQueue", NULL);
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk1");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk2");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk3");
    });

输出结果为

2018-01-08 21:21:27.321695+0800 GCDDemo[15407:286726] blk1
2018-01-08 21:21:27.321915+0800 GCDDemo[15407:286726] blk2
2018-01-08 21:21:27.322035+0800 GCDDemo[15407:286726] blk3

这也就验证了串行队列的特点,按照加入队列的顺序依次执行处理。

  • Main Dispatch Queue
    main dispatch queue是串行队列,其中只有一条线程,即主线程。我们经常需要在其他线程中获取数据然后子安主线程中更新UI,这个时候我们可以
    dispatch_async(dispatch_get_main_queue(), ^{
        
        //更新UI
    });

从中我们也可以看出得到主线程的方法:
dispatch_queue_t mainQueue = dispatch_get_main_queue();

Current_Dispatch_Queue

并行队列的特点是其会创建多条线程。而具体创建多少条线程是由系统决定的。比如现在有代码块blk1,blk2,blk3,blk4,blk5,系统为我们创建了三条线程,那么首先blk1进入线程1执行,blk2进入线程2执行,blk3进入线程3执行,这时当blk1,blk2,blk3这三个代码块,有一个执行完了,blk4就会到这个已经执行完处理的线程中执行,blk5也是这样。所以这五个代码块是没有执行完成的先后顺序的,这个顺序是不确定的。

并行队列适合处理不关心执行先后顺序的操作,这时使用并行队列会提高执行效率。

  • 并行队列的创建
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
  • 下面我们还是用代码来测试一下并行队列
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk1");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk2");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk3");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk4");
    });
    

运行结果

2018-01-08 21:35:35.855027+0800 GCDDemo[15457:292699] blk2
2018-01-08 21:35:35.855027+0800 GCDDemo[15457:292701] blk1
2018-01-08 21:35:35.855034+0800 GCDDemo[15457:292700] blk3
2018-01-08 21:35:35.855044+0800 GCDDemo[15457:292706] blk4

再次运行

2018-01-08 21:37:09.904149+0800 GCDDemo[15478:293936] blk1
2018-01-08 21:37:09.904149+0800 GCDDemo[15478:293938] blk2
2018-01-08 21:37:09.904179+0800 GCDDemo[15478:293937] blk4
2018-01-08 21:37:09.904173+0800 GCDDemo[15478:293939] blk3

根据运行结果我们可以看出并行队列的执行特点,每个处理执行完成的顺序是没有联系的。

  • Global Dispatch Queue
    global dispatch queue即为全局队列,它是并行队列,因此我们在使用并行队列的时候有时候没有必要自己去创建,可以直接把这个全局队列拿来用。
    global Dispatch Queue有4个执行优先级,分别是高优先级(High Priority),默认优先级(default priority),低优先级(low priority),以及后台优先级(background priority)。我们在向global Queue追加处理时,应该选择与处理内容相对应的执行优先级的global Queue。
    global Dispatch Queue的获取方法为
   //高优先级的获取方法
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    //默认优先级的获取方法
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //低优先级的获取方法
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
    //后台优先级的获取方法
    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    

dispatch_after

如果我们想要让某个操作在3秒后执行,那么我们可以尝试使用Dispatch_after.

需要注意的是,dispatch_after并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
    
    dispatch_after(time, dispatch_get_main_queue(), ^{
        
        NSLog(@"在3秒后把我加入队列");
    });

上面的代码即在3秒后把代码块加入主线程,但是并不一定是加入后马上执行。因为main Dispatch Queue是在主线程的runloop中执行,所以在比如每个1/6e0秒执行的runloop中,代码块最快在3秒后执行,最慢在3+1/60秒后执行。即便如此,在想大致延迟执行处理时,还是比较有效的。

Dispatch Group

有时候我们想要在追加到队列中的多个处理执行完成后执行某个操作,比如刷新UI。这个时候如果这个队列是串行队列,那么很容易,只需要把想要追加的操作追加到串行队列最后就可以。那么如果是并行队列就没有那么简单了。这个时候Dispatch Queue就派上用场了。

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, ^{
        
        NSLog(@"blk1")
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"blk2")
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"blk3")
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        //在这里可以执行刷新UI
        
        NSLog(@"全部操作完成");
    });

执行结果

2018-01-08 22:20:50.803237+0800 GCDDemo[15557:311301] blk3
2018-01-08 22:20:50.803237+0800 GCDDemo[15557:311303] blk2
2018-01-08 22:20:50.803237+0800 GCDDemo[15557:311304] blk1
2018-01-08 22:20:50.816573+0800 GCDDemo[15557:311228] 全部操作完成

再次运行

2018-01-08 22:21:26.810922+0800 GCDDemo[15574:312328] blk3
2018-01-08 22:21:26.810922+0800 GCDDemo[15574:312327] blk1
2018-01-08 22:21:26.810922+0800 GCDDemo[15574:312330] blk2
2018-01-08 22:21:26.826096+0800 GCDDemo[15574:312265] 全部操作完成

我们可以看到,虽然前面的三个blk操作没有顺序,但是dispatch_group_notify里面的代码块一定是在最后执行的。在全部操作执行完成后,该代码使用dispatch_group_notify
将执行的blk追加到dispatch_queue。

  • disapatch_group_wait
    在dispatch_group中也可以使用dispatch_group_wait等待全部处理执行结束。
    例如以下源码:
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, ^{
        
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"blk3");
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"全部处理完成");

运行结果

2018-01-09 19:12:01.519348+0800 GCDDemo[6074:210080] blk1
2018-01-09 19:12:01.519376+0800 GCDDemo[6074:210079] blk2
2018-01-09 19:12:01.519411+0800 GCDDemo[6074:210078] blk3
2018-01-09 19:12:01.519506+0800 GCDDemo[6074:209982] 全部处理完成

再次运行

2018-01-09 19:14:10.650129+0800 GCDDemo[6102:212126] blk1
2018-01-09 19:14:10.650129+0800 GCDDemo[6102:212128] blk3
2018-01-09 19:14:10.650129+0800 GCDDemo[6102:212125] blk2
2018-01-09 19:14:10.650341+0800 GCDDemo[6102:212031] 全部处理完成

通过运行结果我们可以看到在dispatch_group_wait后面的代码确实是在前面的处理都完成后才执行。
我们注意到dispatch_group_wait的第二个参数是一个dispatch_time_t类型的参数,这个参数的意思是等待一段时间后再执行后面的操作,这里DISPATCH_TIME_FOREVER意味着永久等待,即如果前面的三个代码块的操作没有处理完则一直等待,直到这三个代码块处理完了再执行接下来的代码。如果这个等待的时间是3秒,则意味着最多等待3秒,3秒内如果前面的代码块全部执行完了则马上接下来执行后面的代码,3秒时间到了如果还没有执行完也开始执行下面的代码。
接着看这个代码块

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, ^{
        
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"blk3");
    });
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10ull*NSEC_PER_SEC);
    
    long result = dispatch_group_wait(group, time);
    
    if(result == 0){
        
        //前面的三个代码块全部处理完成
    }else{
        
        //前面的三个代码块还有处理没有完成
    }

这里dispatch_group_wait是有返回值的。如果result为0,则说明在指定的时间内,前面的代码块全部操作完成,若果不为0则说明前面的代码块还有没有完成。如果提前完成则会提前进行下面的代码。
如果这里的time指定为DISPATCH_TIME_FOREVER,则可想而知,result一定返回为0。

dispatch_barrier_async

比如我们在进行数据库的操作时,写入处理不能和其他的写入处理或者读取处理并行操作。但是如果读取处理只是和读取处理并行执行,那么多个并行处理是不会发生问题的。

dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作1");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作2");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作3");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"执行写入操作");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作4");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作5");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"�读取操作6");
    });

这段代码,由于我们完全无法确定这些读取操作和写入操作的执行顺序,所以就会出现有时候我们想要在写入前读入,但是真正读取的数据却是写入后的数据这种问题。而如果我们的写入操作使用的是dispatch_barrier_async,那么就会在读取操作1,读取操作2,读取操作3全部完成后再执行dispatch_barrier_async中的写入代码。这段写入代码执行完成后再执行后面的读取操作4,读取操作5,读取操作6。这样就能保证读取操作1,读取操作2,读取操作3是读取的写入之前的数据,读取操作4,读取操作5,读取操作6读取的是写入之后的数据。
如下代码:

    dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作1");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作2");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作3");
    });
    
    dispatch_barrier_async(queue, ^{
        
        NSLog(@"执行写入操作");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作4");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"读取操作5");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"�读取操作6");
    });

执行结果

2018-01-09 19:58:26.377586+0800 GCDDemo[6243:237609] 读取操作1
2018-01-09 19:58:26.377586+0800 GCDDemo[6243:237608] 读取操作3
2018-01-09 19:58:26.377586+0800 GCDDemo[6243:237607] 读取操作2
2018-01-09 19:58:26.377960+0800 GCDDemo[6243:237607] 执行写入操作
2018-01-09 19:58:26.378046+0800 GCDDemo[6243:237608] 读取操作5
2018-01-09 19:58:26.378052+0800 GCDDemo[6243:237609] �读取操作6
2018-01-09 19:58:26.378046+0800 GCDDemo[6243:237607] 读取操作4

再次执行

2018-01-09 20:07:56.548108+0800 GCDDemo[6284:243527] 读取操作2
2018-01-09 20:07:56.548108+0800 GCDDemo[6284:243525] 读取操作1
2018-01-09 20:07:56.548112+0800 GCDDemo[6284:243526] 读取操作3
2018-01-09 20:07:56.548292+0800 GCDDemo[6284:243526] 执行写入操作
2018-01-09 20:07:56.548372+0800 GCDDemo[6284:243526] 读取操作4
2018-01-09 20:07:56.548377+0800 GCDDemo[6284:243525] 读取操作5
2018-01-09 20:07:56.548382+0800 GCDDemo[6284:243527] �读取操作6

通过执行结果也可以验证,一定是在读取操作1,读取操作2,读取操作3全部执行完成后再执行写入操作,写入操作执行完成,再执行后面的读取操作。

注意 我在测试的时候发现这个dispatch_barrier_aync对dispatch_global_queue是不起作用的。

dispatch_apply

这个函数按照指定的次数将指定的block追加到指定的dispatch_queue中,并且等待全部处理执行结束。
示例代码

    dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_apply(10, queue, ^(size_t index){
        
        NSLog(@"%zu", index);
    });
    
    NSLog(@"全部执行完成");
    }

执行结果

2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250371] 1
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250281] 0
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250374] 3
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250372] 2
2018-01-09 20:21:31.494867+0800 GCDDemo[6315:250281] 5
2018-01-09 20:21:31.494867+0800 GCDDemo[6315:250374] 4
2018-01-09 20:21:31.494867+0800 GCDDemo[6315:250371] 6
2018-01-09 20:21:31.494924+0800 GCDDemo[6315:250372] 7
2018-01-09 20:21:31.494988+0800 GCDDemo[6315:250281] 8
2018-01-09 20:21:31.494996+0800 GCDDemo[6315:250374] 9
2018-01-09 20:21:31.495437+0800 GCDDemo[6315:250281] 全部执行完成

因为是在并行队列中执行处理,所以执行的次序是不确定的, 但是“全部执行完成”一定是在最后输出。因为dispatch_apply会等待全部执行结束。这一特性也可以用于在执行一系列的操作之后回到主线程中刷新UI。
dispatch_apply中的第一个参数为重读次数,这里的重复次数为10即重复10次,index从0到9.

dispatch_suspend/dispatch_resume

这两个函数的作用分别是把queue挂起和恢复。但是要注意,在执行dispatch_suspend时,已经执行和开始执行但还没有完成的操作都不会受影响,只有已经加入队列但是还没开始执行的处理会停止执行。

需要注意的是,这个操作对dispatch_main_queue和dispatch_global_queue是不起作用的。但是也不会使程序崩溃。
而如果在其他的队列中调用dispatch_suspend而没有调用dispatch_resume这样是会引起崩溃的。
示例代码

dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk1");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk2");
    });
    
    dispatch_suspend(queue);
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10ull*NSEC_PER_SEC);
    
    dispatch_after(time, dispatch_get_main_queue(), ^{
        
        dispatch_resume(queue);
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"blk3");
    });

这段代码先输出不blk1和blk2然后大约十秒后输出blk3。

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

推荐阅读更多精彩内容