最近在学习有关GCD的知识,学习完了想要做个总结,一是加深印象,方便以后查阅,二是拿出来和大家分享一下,一起讨论,一起进步,最后就是希望对于一些跟我一样的iOS初学者有一些帮助。
Dispatch Queue
Serial Dispatch Queue
serial Dispatch Queue即串行队列 串行队列的特点主要就是它只会创建一条线程工作,即单线程工作,我们的主线程即serial Dispatch Queue。
例如我们把三个代码块blk1,blk2,blk3依次加入一个串行队列,么由于这个串行队列只会创建一条线程,而一条线程同时只能执行一个事件,因此首先执行blk1,blk1执行完成之后再执行blk2,blk2执行完成之后再执行blk3,这样依次执行。
在不想改变执行顺序或者不想同时并行执行多个处理时用串行队列。
- 串行队列的创建
使用dispatch_queue_create创建队列
dispatch_queue_t queue = dispatch_queue_create("com.mySerialDispatchQueue", NULL);
dispatch_queue_created的第一个参数是我们创建的队列的标识,第二个参数,如果传入的是NULL,那么就是创建串行队列,如果传入的是DISPATCH_QUEUE_CONCURRENT那么创建的就是并行队列。
- 下面我们用代码测试一下串行队列
dispatch_queue_t queue = dispatch_queue_create("com.mySerialDispatchQueue", NULL);
dispatch_async(queue, ^{
NSLog(@"blk1");
});
dispatch_async(queue, ^{
NSLog(@"blk2");
});
dispatch_async(queue, ^{
NSLog(@"blk3");
});
输出结果为
2018-01-08 21:21:27.321695+0800 GCDDemo[15407:286726] blk1
2018-01-08 21:21:27.321915+0800 GCDDemo[15407:286726] blk2
2018-01-08 21:21:27.322035+0800 GCDDemo[15407:286726] blk3
这也就验证了串行队列的特点,按照加入队列的顺序依次执行处理。
- Main Dispatch Queue
main dispatch queue是串行队列,其中只有一条线程,即主线程。我们经常需要在其他线程中获取数据然后子安主线程中更新UI,这个时候我们可以
dispatch_async(dispatch_get_main_queue(), ^{
//更新UI
});
从中我们也可以看出得到主线程的方法:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
Current_Dispatch_Queue
并行队列的特点是其会创建多条线程。而具体创建多少条线程是由系统决定的。比如现在有代码块blk1,blk2,blk3,blk4,blk5,系统为我们创建了三条线程,那么首先blk1进入线程1执行,blk2进入线程2执行,blk3进入线程3执行,这时当blk1,blk2,blk3这三个代码块,有一个执行完了,blk4就会到这个已经执行完处理的线程中执行,blk5也是这样。所以这五个代码块是没有执行完成的先后顺序的,这个顺序是不确定的。
并行队列适合处理不关心执行先后顺序的操作,这时使用并行队列会提高执行效率。
- 并行队列的创建
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
- 下面我们还是用代码来测试一下并行队列
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"blk1");
});
dispatch_async(queue, ^{
NSLog(@"blk2");
});
dispatch_async(queue, ^{
NSLog(@"blk3");
});
dispatch_async(queue, ^{
NSLog(@"blk4");
});
运行结果
2018-01-08 21:35:35.855027+0800 GCDDemo[15457:292699] blk2
2018-01-08 21:35:35.855027+0800 GCDDemo[15457:292701] blk1
2018-01-08 21:35:35.855034+0800 GCDDemo[15457:292700] blk3
2018-01-08 21:35:35.855044+0800 GCDDemo[15457:292706] blk4
再次运行
2018-01-08 21:37:09.904149+0800 GCDDemo[15478:293936] blk1
2018-01-08 21:37:09.904149+0800 GCDDemo[15478:293938] blk2
2018-01-08 21:37:09.904179+0800 GCDDemo[15478:293937] blk4
2018-01-08 21:37:09.904173+0800 GCDDemo[15478:293939] blk3
根据运行结果我们可以看出并行队列的执行特点,每个处理执行完成的顺序是没有联系的。
- Global Dispatch Queue
global dispatch queue即为全局队列,它是并行队列,因此我们在使用并行队列的时候有时候没有必要自己去创建,可以直接把这个全局队列拿来用。
global Dispatch Queue有4个执行优先级,分别是高优先级(High Priority),默认优先级(default priority),低优先级(low priority),以及后台优先级(background priority)。我们在向global Queue追加处理时,应该选择与处理内容相对应的执行优先级的global Queue。
global Dispatch Queue的获取方法为
//高优先级的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//默认优先级的获取方法
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//低优先级的获取方法
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//后台优先级的获取方法
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_after
如果我们想要让某个操作在3秒后执行,那么我们可以尝试使用Dispatch_after.
需要注意的是,dispatch_after并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"在3秒后把我加入队列");
});
上面的代码即在3秒后把代码块加入主线程,但是并不一定是加入后马上执行。因为main Dispatch Queue是在主线程的runloop中执行,所以在比如每个1/6e0秒执行的runloop中,代码块最快在3秒后执行,最慢在3+1/60秒后执行。即便如此,在想大致延迟执行处理时,还是比较有效的。
Dispatch Group
有时候我们想要在追加到队列中的多个处理执行完成后执行某个操作,比如刷新UI。这个时候如果这个队列是串行队列,那么很容易,只需要把想要追加的操作追加到串行队列最后就可以。那么如果是并行队列就没有那么简单了。这个时候Dispatch Queue就派上用场了。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk1")
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2")
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3")
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//在这里可以执行刷新UI
NSLog(@"全部操作完成");
});
执行结果
2018-01-08 22:20:50.803237+0800 GCDDemo[15557:311301] blk3
2018-01-08 22:20:50.803237+0800 GCDDemo[15557:311303] blk2
2018-01-08 22:20:50.803237+0800 GCDDemo[15557:311304] blk1
2018-01-08 22:20:50.816573+0800 GCDDemo[15557:311228] 全部操作完成
再次运行
2018-01-08 22:21:26.810922+0800 GCDDemo[15574:312328] blk3
2018-01-08 22:21:26.810922+0800 GCDDemo[15574:312327] blk1
2018-01-08 22:21:26.810922+0800 GCDDemo[15574:312330] blk2
2018-01-08 22:21:26.826096+0800 GCDDemo[15574:312265] 全部操作完成
我们可以看到,虽然前面的三个blk操作没有顺序,但是dispatch_group_notify里面的代码块一定是在最后执行的。在全部操作执行完成后,该代码使用dispatch_group_notify
将执行的blk追加到dispatch_queue。
- disapatch_group_wait
在dispatch_group中也可以使用dispatch_group_wait等待全部处理执行结束。
例如以下源码:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"全部处理完成");
运行结果
2018-01-09 19:12:01.519348+0800 GCDDemo[6074:210080] blk1
2018-01-09 19:12:01.519376+0800 GCDDemo[6074:210079] blk2
2018-01-09 19:12:01.519411+0800 GCDDemo[6074:210078] blk3
2018-01-09 19:12:01.519506+0800 GCDDemo[6074:209982] 全部处理完成
再次运行
2018-01-09 19:14:10.650129+0800 GCDDemo[6102:212126] blk1
2018-01-09 19:14:10.650129+0800 GCDDemo[6102:212128] blk3
2018-01-09 19:14:10.650129+0800 GCDDemo[6102:212125] blk2
2018-01-09 19:14:10.650341+0800 GCDDemo[6102:212031] 全部处理完成
通过运行结果我们可以看到在dispatch_group_wait后面的代码确实是在前面的处理都完成后才执行。
我们注意到dispatch_group_wait的第二个参数是一个dispatch_time_t类型的参数,这个参数的意思是等待一段时间后再执行后面的操作,这里DISPATCH_TIME_FOREVER意味着永久等待,即如果前面的三个代码块的操作没有处理完则一直等待,直到这三个代码块处理完了再执行接下来的代码。如果这个等待的时间是3秒,则意味着最多等待3秒,3秒内如果前面的代码块全部执行完了则马上接下来执行后面的代码,3秒时间到了如果还没有执行完也开始执行下面的代码。
接着看这个代码块
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk3");
});
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if(result == 0){
//前面的三个代码块全部处理完成
}else{
//前面的三个代码块还有处理没有完成
}
这里dispatch_group_wait是有返回值的。如果result为0,则说明在指定的时间内,前面的代码块全部操作完成,若果不为0则说明前面的代码块还有没有完成。如果提前完成则会提前进行下面的代码。
如果这里的time指定为DISPATCH_TIME_FOREVER,则可想而知,result一定返回为0。
dispatch_barrier_async
比如我们在进行数据库的操作时,写入处理不能和其他的写入处理或者读取处理并行操作。但是如果读取处理只是和读取处理并行执行,那么多个并行处理是不会发生问题的。
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"读取操作1");
});
dispatch_async(queue, ^{
NSLog(@"读取操作2");
});
dispatch_async(queue, ^{
NSLog(@"读取操作3");
});
dispatch_async(queue, ^{
NSLog(@"执行写入操作");
});
dispatch_async(queue, ^{
NSLog(@"读取操作4");
});
dispatch_async(queue, ^{
NSLog(@"读取操作5");
});
dispatch_async(queue, ^{
NSLog(@"�读取操作6");
});
这段代码,由于我们完全无法确定这些读取操作和写入操作的执行顺序,所以就会出现有时候我们想要在写入前读入,但是真正读取的数据却是写入后的数据这种问题。而如果我们的写入操作使用的是dispatch_barrier_async,那么就会在读取操作1,读取操作2,读取操作3全部完成后再执行dispatch_barrier_async中的写入代码。这段写入代码执行完成后再执行后面的读取操作4,读取操作5,读取操作6。这样就能保证读取操作1,读取操作2,读取操作3是读取的写入之前的数据,读取操作4,读取操作5,读取操作6读取的是写入之后的数据。
如下代码:
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"读取操作1");
});
dispatch_async(queue, ^{
NSLog(@"读取操作2");
});
dispatch_async(queue, ^{
NSLog(@"读取操作3");
});
dispatch_barrier_async(queue, ^{
NSLog(@"执行写入操作");
});
dispatch_async(queue, ^{
NSLog(@"读取操作4");
});
dispatch_async(queue, ^{
NSLog(@"读取操作5");
});
dispatch_async(queue, ^{
NSLog(@"�读取操作6");
});
执行结果
2018-01-09 19:58:26.377586+0800 GCDDemo[6243:237609] 读取操作1
2018-01-09 19:58:26.377586+0800 GCDDemo[6243:237608] 读取操作3
2018-01-09 19:58:26.377586+0800 GCDDemo[6243:237607] 读取操作2
2018-01-09 19:58:26.377960+0800 GCDDemo[6243:237607] 执行写入操作
2018-01-09 19:58:26.378046+0800 GCDDemo[6243:237608] 读取操作5
2018-01-09 19:58:26.378052+0800 GCDDemo[6243:237609] �读取操作6
2018-01-09 19:58:26.378046+0800 GCDDemo[6243:237607] 读取操作4
再次执行
2018-01-09 20:07:56.548108+0800 GCDDemo[6284:243527] 读取操作2
2018-01-09 20:07:56.548108+0800 GCDDemo[6284:243525] 读取操作1
2018-01-09 20:07:56.548112+0800 GCDDemo[6284:243526] 读取操作3
2018-01-09 20:07:56.548292+0800 GCDDemo[6284:243526] 执行写入操作
2018-01-09 20:07:56.548372+0800 GCDDemo[6284:243526] 读取操作4
2018-01-09 20:07:56.548377+0800 GCDDemo[6284:243525] 读取操作5
2018-01-09 20:07:56.548382+0800 GCDDemo[6284:243527] �读取操作6
通过执行结果也可以验证,一定是在读取操作1,读取操作2,读取操作3全部执行完成后再执行写入操作,写入操作执行完成,再执行后面的读取操作。
注意 我在测试的时候发现这个dispatch_barrier_aync对dispatch_global_queue是不起作用的。
dispatch_apply
这个函数按照指定的次数将指定的block追加到指定的dispatch_queue中,并且等待全部处理执行结束。
示例代码
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index){
NSLog(@"%zu", index);
});
NSLog(@"全部执行完成");
}
执行结果
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250371] 1
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250281] 0
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250374] 3
2018-01-09 20:21:31.494752+0800 GCDDemo[6315:250372] 2
2018-01-09 20:21:31.494867+0800 GCDDemo[6315:250281] 5
2018-01-09 20:21:31.494867+0800 GCDDemo[6315:250374] 4
2018-01-09 20:21:31.494867+0800 GCDDemo[6315:250371] 6
2018-01-09 20:21:31.494924+0800 GCDDemo[6315:250372] 7
2018-01-09 20:21:31.494988+0800 GCDDemo[6315:250281] 8
2018-01-09 20:21:31.494996+0800 GCDDemo[6315:250374] 9
2018-01-09 20:21:31.495437+0800 GCDDemo[6315:250281] 全部执行完成
因为是在并行队列中执行处理,所以执行的次序是不确定的, 但是“全部执行完成”一定是在最后输出。因为dispatch_apply会等待全部执行结束。这一特性也可以用于在执行一系列的操作之后回到主线程中刷新UI。
dispatch_apply中的第一个参数为重读次数,这里的重复次数为10即重复10次,index从0到9.
dispatch_suspend/dispatch_resume
这两个函数的作用分别是把queue挂起和恢复。但是要注意,在执行dispatch_suspend时,已经执行和开始执行但还没有完成的操作都不会受影响,只有已经加入队列但是还没开始执行的处理会停止执行。
需要注意的是,这个操作对dispatch_main_queue和dispatch_global_queue是不起作用的。但是也不会使程序崩溃。
而如果在其他的队列中调用dispatch_suspend而没有调用dispatch_resume这样是会引起崩溃的。
示例代码
dispatch_queue_t queue = dispatch_queue_create("com.myCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"blk1");
});
dispatch_async(queue, ^{
NSLog(@"blk2");
});
dispatch_suspend(queue);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
dispatch_resume(queue);
});
dispatch_async(queue, ^{
NSLog(@"blk3");
});
这段代码先输出不blk1和blk2然后大约十秒后输出blk3。