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--- {number = 4, name = (null)}
2018-09-19 20:58:17.810806+0800 GCDDemo[778:21614] download1--- {number = 3, name = (null)}
2018-09-19 20:58:17.810826+0800 GCDDemo[778:21611] download2--- {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--- {number = 3, name = (null)}
2018-09-19 21:07:22.768466+0800 GCDDemo[908:37029] download2--- {number = 4, name = (null)}
2018-09-19 21:07:22.768928+0800 GCDDemo[908:37035] download3--- {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--- {number = 3, name = (null)}
2018-09-19 21:15:03.455426+0800 GCDDemo[1004:50216] download2--- {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--- {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 {number = 1, name = main}
2018-09-19 21:28:40.858465+0800 GCDDemo[1186:71347] ---1 {number = 1, name = main}
2018-09-19 21:28:40.858928+0800 GCDDemo[1186:71347] ---2 {number = 1, name = main}
2018-09-19 21:28:40.859210+0800 GCDDemo[1186:71347] ---3 {number = 1, name = main}
2018-09-19 21:28:40.859446+0800 GCDDemo[1186:71347] ---4 {number = 1, name = main}
2018-09-19 21:28:40.859568+0800 GCDDemo[1186:71347] ---5 {number = 1, name = main}
2018-09-19 21:28:40.859755+0800 GCDDemo[1186:71347] ---6 {number = 1, name = main}
2018-09-19 21:28:40.859855+0800 GCDDemo[1186:71347] ---7 {number = 1, name = main}
2018-09-19 21:28:40.859950+0800 GCDDemo[1186:71347] ---8 {number = 1, name = main}
2018-09-19 21:28:40.860457+0800 GCDDemo[1186:71347] ---9 {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 {number = 1, name = main}
2018-09-19 21:51:30.086315+0800 GCDDemo[1457:102441] ---1 {number = 3, name = (null)}
2018-09-19 21:51:30.086360+0800 GCDDemo[1457:102438] ---2 {number = 4, name = (null)}
2018-09-19 21:51:30.086413+0800 GCDDemo[1457:102440] ---3 {number = 5, name = (null)}
2018-09-19 21:51:30.087017+0800 GCDDemo[1457:102416] ---4 {number = 1, name = main}
2018-09-19 21:51:30.087055+0800 GCDDemo[1457:102441] ---5 {number = 3, name = (null)}
2018-09-19 21:51:30.087106+0800 GCDDemo[1457:102438] ---6 {number = 4, name = (null)}
2018-09-19 21:51:30.087937+0800 GCDDemo[1457:102440] ---7 {number = 5, name = (null)}
2018-09-19 21:51:30.088128+0800 GCDDemo[1457:102416] ---8 {number = 1, name = main}
2018-09-19 21:51:30.088153+0800 GCDDemo[1457:102441] ---9 {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-----{number = 8, name = (null)}
2018-09-19 22:33:29.229105+0800 GCDDemo[1815:166538] 任务1-----{number = 7, name = (null)}
2018-09-19 22:33:29.229175+0800 GCDDemo[1815:166710] 任务3-----{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 --- {number = 4, name = (null)}
2018-09-19 22:38:13.734380+0800 GCDDemo[1932:174535] 任务1 --- {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 --- {number = 5, name = (null)}
2018-09-19 22:38:20.648144+0800 GCDDemo[1932:174806] 任务2 --- {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 --- {number = 3, name = (null)}
2018-09-20 09:59:11.834493+0800 GCDDemo[1305:87498] 网络请求2 --- {number = 4, name = (null)}
2018-09-20 09:59:11.835226+0800 GCDDemo[1305:87495] 网络请求3 --- {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 --- {number = 4, name = (null)}
2018-09-20 09:59:13.714673+0800 GCDDemo[1305:87495] 网络请求2 --- {number = 3, name = (null)}
2018-09-20 09:59:13.715509+0800 GCDDemo[1305:87495] 网络请求3 --- {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苹果文档的解释:

iOS面试--GCD常见用法_第1张图片
图2.png

你可能感兴趣的:(iOS面试--GCD常见用法)