iOS开发

iOS面试--GCD常见用法

2018-09-20  本文已影响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)}

控制队列里面任务的执行顺序。现在队列里面的任务是并发执行的,没有顺序,有可能是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)}

此时我们发现,并非是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.

这里,栅栏函数前面有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-----


场景:需要下载两张图片(图片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------

 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,可以简洁的实现上述功能。

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

图2.png
上一篇下一篇

猜你喜欢

热点阅读