iOS面试--GCD常见用法

项目中常见的GCD用法有已下几种:

1.GCD栅栏函数
2.GCD快速迭代(遍历)
3.GCD队列组的使用

1.GCD栅栏函数
例子1:

先来看一个全局并发队列的代码:

    // 获得全局并发队列
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //1.异步函数
    dispatch_async(queue, ^{
        NSLog(@"download1--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"download2--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"download3--- %@",[NSThread currentThread]);
    });
    

查看控制台打印输出如下:

2018-09-19 20:58:17.810763+0800 GCDDemo[778:21612] download3--- <NSThread: 0x600000466000>{number = 4, name = (null)}
2018-09-19 20:58:17.810806+0800 GCDDemo[778:21614] download1--- <NSThread: 0x600000463e00>{number = 3, name = (null)}
2018-09-19 20:58:17.810826+0800 GCDDemo[778:21611] download2--- <NSThread: 0x60400026d440>{number = 5, name = (null)}

  • Tips:

控制队列里面任务的执行顺序。现在队列里面的任务是并发执行的,没有顺序,有可能是3-2-1或者2-1-3的顺序,但是如果我们需要规定必须download1和download2执行完毕后,再执行download3,这时候就需要用到栅栏函数。

    // 获得全局并发队列
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //1.异步函数
    dispatch_async(queue, ^{
        NSLog(@"download1--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"download2--- %@",[NSThread currentThread]);
    });
    // 栅栏函数
    dispatch_barrier_sync(queue, ^{
        NSLog(@"++++++++++++++++++++");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3--- %@",[NSThread currentThread]);
    });

运行并查看控制台打印输出如下:

2018-09-19 21:07:22.768334+0800 GCDDemo[908:36593] ++++++++++++++++++++
2018-09-19 21:07:22.768467+0800 GCDDemo[908:36621] download1--- <NSThread: 0x600000271b40>{number = 3, name = (null)}
2018-09-19 21:07:22.768466+0800 GCDDemo[908:37029] download2--- <NSThread: 0x60400047c300>{number = 4, name = (null)}
2018-09-19 21:07:22.768928+0800 GCDDemo[908:37035] download3--- <NSThread: 0x60400047af80>{number = 5, name = (null)}

  • Tips:

此时我们发现,并非是download1 ,download2,+++++++,download3 的顺序。这里有一个小坑,因为栅栏函数不能使用全局并发队列,所以需要使用自己创建的并发队列。所以这里将dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);修改为dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

再次运行结果如下:

2018-09-19 21:15:03.455425+0800 GCDDemo[1004:50215] download1--- <NSThread: 0x6000004612c0>{number = 3, name = (null)}
2018-09-19 21:15:03.455426+0800 GCDDemo[1004:50216] download2--- <NSThread: 0x604000277440>{number = 4, name = (null)}
2018-09-19 21:15:03.455606+0800 GCDDemo[1004:50066] ++++++++++++++++++++
2018-09-19 21:15:03.456771+0800 GCDDemo[1004:50216] download3--- <NSThread: 0x604000277440>{number = 4, name = (null)}

这里的执行结果和我们想像中是一模一样的了,download1和download2执行完毕,然后执行栅栏函数,栅栏函数执行完毕之后,就会执行download3.

  • Tips:

这里,栅栏函数前面有download1和download2两个函数,但是这两个函数的顺序是无法控制的,哪个先执行完,哪个后执行完,只是没有办法控制的,因为download1和download2之间是异步的,可在异步函数中加入for循环测试.

测试代码如下:

  // 获得全局并发队列
    // 栅栏函数不能使用全局并发队列
//   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
    //1.异步函数
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 100; i++) {
            NSLog(@"download1---%ld %@",i,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 100; i++) {
            NSLog(@"download2---%ld %@",i,[NSThread currentThread]);
        }
    });
    // 栅栏函数
    dispatch_barrier_sync(queue, ^{
        NSLog(@"++++++++++++++++++++");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3--- %@",[NSThread currentThread]);
    });
    

结果如下图所示:


1.png
2.GCD快速迭代
例子2:

先来看一个for循环的代码:

for (NSInteger i = 0; i < 10; i++) {
   NSLog(@"---%ld %@",i,[NSThread currentThread]);
}

控制台输出结果:

2018-09-19 21:28:40.857530+0800 GCDDemo[1186:71347] ---0 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.858465+0800 GCDDemo[1186:71347] ---1 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.858928+0800 GCDDemo[1186:71347] ---2 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.859210+0800 GCDDemo[1186:71347] ---3 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.859446+0800 GCDDemo[1186:71347] ---4 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.859568+0800 GCDDemo[1186:71347] ---5 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.859755+0800 GCDDemo[1186:71347] ---6 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.859855+0800 GCDDemo[1186:71347] ---7 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.859950+0800 GCDDemo[1186:71347] ---8 <NSThread: 0x60400006f580>{number = 1, name = main}
2018-09-19 21:28:40.860457+0800 GCDDemo[1186:71347] ---9 <NSThread: 0x60400006f580>{number = 1, name = main}

所以,for循环本身是同步的,内部所有的任务都是串行执行的,一个执行完毕,再执行下一个,内部都是主线程,同一个线程,并没有换子线程。

接着,我们来看下GCD里面的快速迭代,这里我们通过dispatch_apply函数来操作。

    /**
     快速迭代

     @param iterations#> 遍历的次数
     @param queue#> 队列(只能是并发队列)--如果传主队列会发生死锁 如果传串行队列,没有任何作用
     @param size_t 索引
     @return <#return value description#>
     */
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"---%ld %@",index,[NSThread currentThread]);
    });

查看控制台输出结果:

2018-09-19 21:51:30.086209+0800 GCDDemo[1457:102416] ---0 <NSThread: 0x6040000688c0>{number = 1, name = main}
2018-09-19 21:51:30.086315+0800 GCDDemo[1457:102441] ---1 <NSThread: 0x604000273440>{number = 3, name = (null)}
2018-09-19 21:51:30.086360+0800 GCDDemo[1457:102438] ---2 <NSThread: 0x604000273500>{number = 4, name = (null)}
2018-09-19 21:51:30.086413+0800 GCDDemo[1457:102440] ---3 <NSThread: 0x604000274140>{number = 5, name = (null)}
2018-09-19 21:51:30.087017+0800 GCDDemo[1457:102416] ---4 <NSThread: 0x6040000688c0>{number = 1, name = main}
2018-09-19 21:51:30.087055+0800 GCDDemo[1457:102441] ---5 <NSThread: 0x604000273440>{number = 3, name = (null)}
2018-09-19 21:51:30.087106+0800 GCDDemo[1457:102438] ---6 <NSThread: 0x604000273500>{number = 4, name = (null)}
2018-09-19 21:51:30.087937+0800 GCDDemo[1457:102440] ---7 <NSThread: 0x604000274140>{number = 5, name = (null)}
2018-09-19 21:51:30.088128+0800 GCDDemo[1457:102416] ---8 <NSThread: 0x6040000688c0>{number = 1, name = main}
2018-09-19 21:51:30.088153+0800 GCDDemo[1457:102441] ---9 <NSThread: 0x604000273440>{number = 3, name = (null)}

Tips:通过查看控制台,可发现number=1,3,4,5,for循环number=1,都在主线程执行。dispatch_apply这个内部会开子线程,由主线程和子线程来并发执行任务。

3.GCD队列组的使用
例子3:

先来看一个队列组的代码:

    // 1.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 2.创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 3.异步函数
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务1-----%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务2-----%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务3-----%@",[NSThread currentThread]);
    });
    // 拦截通知,当队列组中,所有的任务都执行完毕的时候,会进入到下面的方法
    dispatch_group_notify(group, queue, ^{
        NSLog(@"-------dispatch_group_notify-----");
    });

查看控制台运行结果:

2018-09-19 22:33:29.229105+0800 GCDDemo[1815:166539] 任务2-----<NSThread: 0x604000261b00>{number = 8, name = (null)}
2018-09-19 22:33:29.229105+0800 GCDDemo[1815:166538] 任务1-----<NSThread: 0x604000261180>{number = 7, name = (null)}
2018-09-19 22:33:29.229175+0800 GCDDemo[1815:166710] 任务3-----<NSThread: 0x600000470dc0>{number = 9, name = (null)}
2018-09-19 22:33:29.229569+0800 GCDDemo[1815:166538] -------dispatch_group_notify-----

这里我们可以发现,我们能保证,当我执行dispatch_group_notify这个block块里面的内容的时候,该队列组里面所有的内容都执行完毕了。

组队列的另一种等同写法如下:

-(void)group2 {
    // 1.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 2.创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 3.在该方法后面的异步函数,会被纳入到队列组的监听范围内
    // dispatch_group_enter | dispatch_group_leave必须配对使用
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"任务1 --- %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"任务2 --- %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"-------dispatch_group_notify-----");
    });
}

输出结果也是相同的:

2018-09-19 22:38:13.734329+0800 GCDDemo[1932:174533] 任务2 --- <NSThread: 0x604000466080>{number = 4, name = (null)}
2018-09-19 22:38:13.734380+0800 GCDDemo[1932:174535] 任务1 --- <NSThread: 0x6000002633c0>{number = 3, name = (null)}
2018-09-19 22:38:13.735314+0800 GCDDemo[1932:174535] -------dispatch_group_notify-----
2018-09-19 22:38:20.648059+0800 GCDDemo[1932:174536] 任务1 --- <NSThread: 0x60000007c200>{number = 5, name = (null)}
2018-09-19 22:38:20.648144+0800 GCDDemo[1932:174806] 任务2 --- <NSThread: 0x60000007e440>{number = 6, name = (null)}
2018-09-19 22:38:20.649043+0800 GCDDemo[1932:174806] -------dispatch_group_notify-----


  • 队列组的应用场景01

场景:需要下载两张图片(图片1,图片2),当两张图片下载完成后,合成图片,并且显示图片:

dispatch_queue_t queue = dispatch_queue_create("custom_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
    
dispatch_group_enter(group);
    // 1.下载图片1,开子线程
    dispatch_group_async(group, queue, ^{
        //结束之后要leave
        dispatch_group_leave(group);

        // 1.1 确定url
        NSURL *url = [NSURL URLWithString:@""];
        // 1.2 下载二进制数据
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        // 1.3 转换图片
        UIImage *image1 = [UIImage imageWithData:imageData];
        NSLog(@"image1");
    });
    
  dispatch_group_enter(group);
    // 2.下载图片2,开子线程
    dispatch_group_async(group, queue, ^{
        //结束之后要leave
        dispatch_group_leave(group);

        // 2.1 确定url
        NSURL *url = [NSURL URLWithString:@""];
        // 2.2 下载二进制数据
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        // 2.3 转换图片
        UIImage *image2 = [UIImage imageWithData:imageData];
        NSLog(@"image2");
    });
    
    // 3.合并图片
    dispatch_group_notify(group, queue, ^{
        // 3.1 创建图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        // 3.2画图1
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        // 3.3画图2
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        // 3.4 根据上下文得到一张图片
        UIImage *image =  UIGraphicsGetImageFromCurrentImageContext();
        // 3.5 关闭上下文
        UIGraphicsEndImageContext();
        // 3.6 更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"------更新UI------");
        });
    });

控制台输出如下:

2018-09-19 23:00:28.387944+0800 GCDDemo[2194:206824] image2
2018-09-19 23:00:28.388420+0800 GCDDemo[2194:206826] image1
2018-09-19 23:00:28.389267+0800 GCDDemo[2194:206780] ------更新UI------

  • 队列组的应用场景02
    场景:某界面存在多个请求,希望请求依次执行,比如有三个请求,分别对应 网络请求1 , 网络请求2, 网络请求3,现在需要按照1-2-3的顺序执行。
    解解决此问题的方法可通过信号量dispatch_semaphore进行解决。我们将请求方法替换为添加dispatch_semaphore限制的形式,代码如下:
 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"网络请求1 --- %@",[NSThread currentThread]);
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    
    dispatch_async(queue, ^{
        NSLog(@"网络请求2 --- %@",[NSThread currentThread]);
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

    dispatch_async(queue, ^{
        NSLog(@"网络请求3 --- %@",[NSThread currentThread]);
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    NSLog(@"------------- end --------------");

执行结果如下:

2018-09-20 09:59:11.834059+0800 GCDDemo[1305:87495] 网络请求1 --- <NSThread: 0x60400046d1c0>{number = 3, name = (null)}
2018-09-20 09:59:11.834493+0800 GCDDemo[1305:87498] 网络请求2 --- <NSThread: 0x60000027f240>{number = 4, name = (null)}
2018-09-20 09:59:11.835226+0800 GCDDemo[1305:87495] 网络请求3 --- <NSThread: 0x60400046d1c0>{number = 3, name = (null)}
2018-09-20 09:59:11.835403+0800 GCDDemo[1305:87387] ------------- end --------------
2018-09-20 09:59:13.714376+0800 GCDDemo[1305:87498] 网络请求1 --- <NSThread: 0x60000027f240>{number = 4, name = (null)}
2018-09-20 09:59:13.714673+0800 GCDDemo[1305:87495] 网络请求2 --- <NSThread: 0x60400046d1c0>{number = 3, name = (null)}
2018-09-20 09:59:13.715509+0800 GCDDemo[1305:87495] 网络请求3 --- <NSThread: 0x60400046d1c0>{number = 3, name = (null)}
2018-09-20 09:59:13.715639+0800 GCDDemo[1305:87387] ------------- end --------------

通过观察输出结果可知,顺序为1--2--3,再次重复运行,我们会发现每次运行结果均一致1--2--3三任务异步顺序执行(1--> 2--> 3)

注: 现在有一个非常流行的链式框架PromiseKit,可以简洁的实现上述功能。

  • Tips
    使用create函数创建的并发队列和全局并发队列的区别:
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

1). 全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级,默认优先级,低优先级和后台优先级一共四个并发队列。我们只是选择其中的一个直接拿来用。而create函数是实打实的从头开始开始去创建一个队列
2). 在iOS6.0之前,在GCD中凡是使用了带create和retain的函数,在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就好了。
3). 在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在使用create函数,即自己创建的并发队列一起使用的时候才有效。具体原因见图2苹果文档的解释:

图2.png

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

推荐阅读更多精彩内容