美文网首页
iOS GCD(二)

iOS GCD(二)

作者: 爬树的蚂蚁 | 来源:发表于2018-10-08 17:00 被阅读8次
    dispatch_after

    如果想要在一段时间之后执行任务,那用dispatch_after函数就可以实现,函数使用如下

        dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t  _Nonnull queue#>, ^{
            
        });
    

    两个参数,一个是dispatch_time_t类型的参数,另一个是dispatch_queue_t类型的参数。很容易理解,就是在指定的时间后在对应的队列中执行任务。

    dispatch_after函数的使用在xcode中有一个代码块,使用起来很方便,输入“ dispatch_after”,然后选择“ dispatch_after snippet...”。

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //添加在n 秒后要执行的任务
        });
    
    注意:

    dispatch_after函数并不是在指定时间之后执行任务,而是在指定的时间之后将任务追加到队列中(这里的例子是主队列)。

    dispatch_time_t

    dispatch_time_t用于表示时间(绝对时间和相对时间),使用dispatch_time函数创建,如下

        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
    
    

    其中第一个参数 DISPATCH_TIME_NOW ,是一个dispatch_time_t类型的值,表示现在的时间,第二个是 int64_t(其实就是长长整型)类型的参数,可以理解 NSEC_PER_SEC 为秒的单位,另一个宏NSEC_PER_MSEC为毫秒的单位。

    函数dispatch_time函数得到的是从第一个参数表示的时间开始,到第二个参数表示的时间间隔之后的这个时间片段。这是一个相对时间,上面有提到过绝对时间,像20180-09-11 10:00:00就是一个绝对时间。

    Dispatch Group

    开发中经常会有这样的场景出现:希望某些任务全部执行完成(尤其是这些任务是并行的),再执行其他的任务。这个时候dispatch group可以实现这样的需求。
    先看一段代码

        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_async(group, globalQueue, ^{
            NSLog(@"执行1");
        });
        dispatch_group_async(group, globalQueue, ^{
            NSLog(@"执行2");
        });
        dispatch_group_notify(group, globalQueue, ^{
            NSLog(@"执行3");
        });
    

    函数dispatch_group_create()为创建一个group,该函数得到的是一个dispatch_group_t 类型的变量;
    函数dispatch_group_async是将追加的block添加到queue中(因为执行还是在queue中执行),同时block持有group,当block执行完成,会释放持有group;
    函数dispatch_group_notify的意思是监听到group中的任务全部执行完成(没有block持有group时),就执行追加到queue(函数第二个参数)中的任务。执行结果如下

    2018-10-08 15:50:18.031517+0800 GCDTest[5179:1545704] 执行1
    2018-10-08 15:50:18.031517+0800 GCDTest[5179:1545705] 执行2
    2018-10-08 15:50:18.031724+0800 GCDTest[5179:1545705] 执行3
    

    但是像下面的写法是不能达到目的的

        dispatch_async(globalQueue, ^{
            NSLog(@"执行1");
        });
        dispatch_async(globalQueue, ^{
            NSLog(@"执行2");
        });
        dispatch_group_async(group, globalQueue, ^{
            
        });
        dispatch_group_notify(group, globalQueue, ^{
            NSLog(@"执行3");
        });
    

    结果如下:

    2018-10-08 15:52:51.403541+0800 GCDTest[5210:1550241] 执行1
    2018-10-08 15:52:51.403541+0800 GCDTest[5210:1550245] 执行3
    2018-10-08 15:52:51.403541+0800 GCDTest[5210:1550244] 执行2
    

    很明显,dispatch_group_notify中的block先于globalQueue中的block执行。因为globalQueue中的block并不持有group,所以两者之间没有关联。

    另外,dispatch_group_notify中的第二个参数,可以是任何一个queue,不一定是dispatch_group_async中的queue。

    dispatch_group_wait

    先看函数dispatch_group_wait的写法

    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)));
    
    

    根据参数可以理解为,等待group执行1秒,不管group中任务是否完成,都往下执行。也可以写成这样

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    

    等到group中的任务执行完成,再往下执行。
    dispatch_group_wait返回值是一个long类型的值,如果group中的任务执行完成,则返回为0,否则不为0(当然,如果第二个参数为DISPATCH_TIME_FOREVER,那返回值必定是0)。看一下完整使用

        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        dispatch_group_async(group, globalQueue, ^{
            NSLog(@"执行1");
        });
        dispatch_group_async(group, globalQueue, ^{
            for (int i = 0; i < 1000; i++) {
                NSLog(@"执行2");
            }
        });
    
        //时间间隔为1毫秒
        long result = dispatch_group_wait(group,dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_MSEC)));
        if (!result) {
            NSLog(@"group中的任务执行完成");
        }else{
            NSLog(@"group中的任务未完成");
        }
    

    注意:dispatch_time函数中的第二个参数为毫秒 NSEC_PER_MSEC

    这里的wait 指的是在指定的时间间隔内执行dispatch_group_wait线程是停止的

    所以在主线程中,一般是像下面这样写

      dispatch_group_wait(group, DISPATCH_TIME_NOW);
    

    这样主线程Runloop的每次循环中,都会检查执行是否结束,从而不会耗费多余的等待时间。但是不建议在主线中使用该函数

    另外再介绍两个API

    dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。
    dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。

    一般是成对出现的, 进入一次,就得离开一次。也就是说,当离开和进入的次数相同时,就代表任务组完成了。如果enterleave多,那就是没完成,如果leave调用的次数错了, 会崩溃的;

    dispatch_barrier_async

    该函数的功能是可以防止数据竞争,与Dispatch Queue配合使用可高效的进行数据库的数据访问或文件访问。
    先看一段代码

        dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读1");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读2");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读3");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"写1");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读4");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读5");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读6");
        });
    

    打印结果如下

    2018-10-08 16:44:37.092300+0800 GCDTest[5694:1622101] 读3
    2018-10-08 16:44:37.092300+0800 GCDTest[5694:1622098] 写1
    2018-10-08 16:44:37.092300+0800 GCDTest[5694:1622100] 读1
    2018-10-08 16:44:37.092336+0800 GCDTest[5694:1622099] 读2
    2018-10-08 16:44:37.092485+0800 GCDTest[5694:1622099] 读4
    2018-10-08 16:44:37.092502+0800 GCDTest[5694:1622100] 读6
    2018-10-08 16:44:37.092513+0800 GCDTest[5694:1622101] 读5
    

    很显然,读到的数据跟预期的相背离,“读1”和“读2”得到的是“写1”后的数据。这就发生了数据竞争了,虽然也有办法可防止这样的事情发生,但是代码会相对复杂,使用dispatch_barrier_async 一行代码可以搞定,看下面的写法

        dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读1");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读2");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读3");
        });
        dispatch_barrier_sync(concurrentQueue, ^{
            NSLog(@"写1");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读4");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读5");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"读6");
        });
    

    结果如下

    2018-10-08 16:51:26.669975+0800 GCDTest[5748:1632783] 读3
    2018-10-08 16:51:26.669975+0800 GCDTest[5748:1632781] 读2
    2018-10-08 16:51:26.669974+0800 GCDTest[5748:1632782] 读1
    2018-10-08 16:51:26.670186+0800 GCDTest[5748:1632739] 写1
    2018-10-08 16:51:26.670305+0800 GCDTest[5748:1632781] 读4
    2018-10-08 16:51:26.670339+0800 GCDTest[5748:1632782] 读5
    2018-10-08 16:51:26.670341+0800 GCDTest[5748:1632784] 读6
    

    这样就保证了,读和写按照预期的顺序来。
    dispatch_barrier_sync函数的作用就是,等待在这个函数之前追加到Concurrent Queue中任务执行完成,然后执行这个函数中的任务,执行完成才往后执行。

    dispatch_apply

    dispatch_apply函数可以理解为是dispatch_sync和Dispatch Group的功能综合体API,先看写法

    dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t  _Nullable queue#>, <#^(size_t)block#>)
    

    总共三个参数

    • 第一个是size_t类型,可以理解为重复追加任务次数;
    • 第二个是dispatch_queue_t类型,即任务的队列;
    • 第三个是带size_t类型参数的block,即任务,其中block的参数为执行任务的角标

    dispatch_apply函数的作用是,将追加到指定队列的任务执行指定次数,并且等待全部执行完毕,再继续往下执行。

    完整用法

        NSArray *arr = @[@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H"];
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //异步添加到全局队列,执行dispatch_apply中的任务
        dispatch_async(globalQueue, ^{
            dispatch_apply([arr count], globalQueue, ^(size_t index) {
                
                NSLog(@"%ld -- %@",index,arr[index]);
                
            });
            //dispatch_apply处理完成,回到主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                
            });
        });
    

    运行结果

    2018-10-09 09:50:31.523513+0800 GCDTest[6956:1910986] 0 -- A
    2018-10-09 09:50:31.523514+0800 GCDTest[6956:1910984] 1 -- B
    2018-10-09 09:50:31.523543+0800 GCDTest[6956:1910985] 2 -- C
    2018-10-09 09:50:31.523689+0800 GCDTest[6956:1910984] 4 -- E
    2018-10-09 09:50:31.523689+0800 GCDTest[6956:1910986] 3 -- D
    2018-10-09 09:50:31.523776+0800 GCDTest[6956:1910985] 5 -- F
    2018-10-09 09:50:31.523821+0800 GCDTest[6956:1910984] 6 -- G
    2018-10-09 09:50:31.523825+0800 GCDTest[6956:1910986] 7 -- H
    

    总结有两点

    • dispatch_apply函数和dispatch_sync函数一样,都会等待追加的任务全部执行完成,所以应当使用dispatch_async函数将 dispatch_apply追加到Concurrent Queue(Global Queue)中 。
    • 如果dispatch_apply第二个参数是Concurrent Queue,那么追加的任务执行的顺序时无序的,不会按照角标的顺序先后执行。但是第二个参数是 Serial Queue,那执行结果将是有序的。

    相关文章

      网友评论

          本文标题:iOS GCD(二)

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