iOS面试--GCD常见用法
项目中常见的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)}
- Tips:
控制队列里面任务的执行顺序。现在队列里面的任务是并发执行的,没有顺序,有可能是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)}
- Tips:
此时我们发现,并非是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.
- Tips:
这里,栅栏函数前面有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-----
- 队列组的应用场景01
场景:需要下载两张图片(图片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------
-
队列组的应用场景02
场景:某界面存在多个请求,希望请求依次执行,比如有三个请求,分别对应 网络请求1 , 网络请求2, 网络请求3,现在需要按照1-2-3的顺序执行。
解解决此问题的方法可通过信号量dispatch_semaphore
进行解决。我们将请求方法替换为添加dispatch_semaphore
限制的形式,代码如下:
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,可以简洁的实现上述功能。
-
Tips
使用create函数创建的并发队列和全局并发队列的区别:
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). 全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级,默认优先级,低优先级和后台优先级一共四个并发队列。我们只是选择其中的一个直接拿来用。而create函数是实打实的从头开始开始去创建一个队列
图2.png
2). 在iOS6.0之前,在GCD中凡是使用了带create和retain的函数,在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就好了。
3). 在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在使用create函数,即自己创建的并发队列一起使用的时候才有效。具体原因见图2苹果文档的解释: