美文网首页iOS开发
iOS面试--GCD常见用法

iOS面试--GCD常见用法

作者: 翀鹰精灵 | 来源:发表于2018-09-20 22:29 被阅读120次
    项目中常见的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_group_t group = dispatch_group_create();
        // 获得并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        // 1.下载图片1,开子线程
        dispatch_group_async(group, queue, ^{
            // 1.1 确定url
            NSURL *url = [NSURL URLWithString:@""];
            // 1.2 下载二进制数据
            NSData *imageData = [NSData dataWithContentsOfURL:url];
            // 1.3 转换图片
            UIImage *image1 = [UIImage imageWithData:imageData];
            NSLog(@"image1");
        });
        
        // 2.下载图片2,开子线程
        dispatch_group_async(group, queue, ^{
            // 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)

    通过观察输出结果可知,顺序为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

    相关文章

      网友评论

        本文标题:iOS面试--GCD常见用法

        本文链接:https://www.haomeiwen.com/subject/kjuwnftx.html