iOS GCD(二)

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(二))