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中移除了。
一般是成对出现的, 进入一次,就得离开一次。也就是说,当离开和进入的次数相同时,就代表任务组完成了。如果enter
比leave
多,那就是没完成,如果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,那执行结果将是有序的。
网友评论