18、iOS底层分析 - GCD(一)基本概念与简单使用
GCD
介绍
GCD 全程 Grand Central Dispatch,由C 语言实现,是苹果为多核的并行运行运算提出的解决方案,GCD 会自动利用更多的CPU 内核,自动管理线程的生命周期。GCD 实现的库是 libdispatch,可以在 Apple Open Source 下载 源码
创建线程、调度任务、销毁线程都是由GCD自己进行管理,只需要告诉GCD 想要执行什么任务,不需要编写任何线程管理代码。
GCD中的两大核心
任务:执行什么操作
队列:用来存放任务
GCD的官方说明
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
定制任务:想要做的事情
将任务添加到队列中
队列
dispatch_queue_t (队列)
在使用GCD 的时候,都避不开dispatch_queue_t (队列),可以说 queue 是使用 GCD 的基础和前提。
Dispatch Queue通过结构体和链表,被实现为FIFO(先进先出)队列。FIFO队列主要是负责管理通过 dispatch_async 等函数所追加的一系列 Blocks。所以我们可以理解为一旦我们在程序中由上到下追加了一组Blocks,那么排除掉 dispatch_after(延迟执行),其内部的追加过程是一个先进先出原则。
但是Block本身并不是直接加入到这个FIFO队列中,而是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后再进入FIFO队列。该结构体用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文(execution context)。
//创建队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attire);
创建一个queue(队列)label:用来标识 queue 的字符串attr:队列属性,DISPATCH_QUEUE_SERIAL 或 NULL 会创建串行队列, DISPATCH_QUEUE_CONCURRENT 为并行队列
//示例
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.gcd.queueCreate.mySerialQueue", NULL);
创建队列
1、第一个参数是队列名称,一般采用域名反转的命名规则,便于调试
2、第二个参数用于区分创建串行队列还是并行队列。
串行队列:传入 NULL 或 DISPATCH_QUEUE_SERIAL
并行队列:传入 DISPATCH_QUEUE_CONCURRENT
3、优先级是默认优先级 即 DISPATCH_QUEUE_PRIORITY_DEFAULTD (0 优先级-默认)
获取队列
三种方式
创建队列也是一种获取队列的方式,除了创建队列,系统标准提供的还有 主队列(Main Dispatch Queue)和 全局队列(Global Dispatch Queue),
1、获取主队列
/** Main Dispatch Queue 的获取方法*/dispatch_queue_t mainQueue = dispatch_get_main_queue();
主队列是在应用程序启动时,main()函数执行之前由系统自动创建的,这个queue被绑定在主线程上。主线程又常被称为 UI线程,所有的UI操作都应在这个线程中进行,主队列是一个串行队列。(当然在其他线程上操作UI是不会报错的,但是可能会有其他问题出现)
// 异步执行主队列刷新UIdispatch_async(dispatch_get_main_queue(), ^{self.view.backgroundColor = [UIColor grayColor];});
2、创建队列
//创建一个并发队列dispatch_queue_t dataQueue1 = dispatch_queue_create("dataQueue", DISPATCH_QUEUE_CONCURRENT);//创建一个串行队列dispatch_queue_t dataQueue2 = dispatch_queue_create("dataQueue", DISPATCH_QUEUE_SERIAL);dispatch_async(dataQueue1, ^{NSLog(@"异步执行 并行队列 处理数据,然后通知主线程刷新UI");int a= 200;for (int i=0; i<100; i++) {a++;NSLog(@"异步执行 并行队列 %d",a);}//通知主线程去刷新UI[self performSelector:@selector(updataUI:) withObject:[NSString stringWithFormat:@"处理后的数据1 %d",a]];});dispatch_sync(dataQueue1, ^{NSLog(@"同步执行 并行队列 处理数据,然后通知主线程刷新UI");int a= 0;for (int i=0; i<100; i++) {a++;NSLog(@"同步执行 并行队列 %d",a);}[self performSelector:@selector(updataUI:) withObject:[NSString stringWithFormat:@"处理后的数据2 %d",a]];});
3、 获取全局队列
dispatch_get_global_queue 文档
/** Global Dispatch Queue (高优先级)的获取方法*/dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);/** Global Dispatch Queue (默认优先级)的获取方法*/dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);/** Global Dispatch Queue (低优先级)的获取方法*/dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);/** Global Dispatch Queue (后台优先级)的获取方法*/dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//获取默认优先级的全局队列dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//获取优先级为后台优先级的全局队列dispatch_queue_t globalDefaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);//变更优先级dispatch_set_target_queue(dataQueue, globalDefaultQueue);
dispatch_set_target_queue 后面做分析。
第一个参数是指明队列执行的优先级
//dispatch_get_global_queue(0, 0) 根据入参计算出索引,从_dispatch_root_queues[]数组中取出对应的queue:
struct dispatch_queue_global_s _dispatch_root_queues[] = {_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,.dq_label = "com.apple.root.maintenance-qos", .dq_serialnum = 4,),_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,.dq_label = "com.apple.root.maintenance-qos.overcommit", .dq_serialnum = 5, ),_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0, .dq_label = "com.apple.root.background-qos", .dq_serialnum = 6, ),_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT, .dq_label = "com.apple.root.background-qos.overcommit", .dq_serialnum = 7, ),_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0, .dq_label = "com.apple.root.utility-qos", .dq_serialnum = 8, ),_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT, .dq_label = "com.apple.root.utility-qos.overcommit", .dq_serialnum = 9, ),_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK, .dq_label = "com.apple.root.default-qos", .dq_serialnum = 10, ),_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT, .dq_label = "com.apple.root.default-qos.overcommit", .dq_serialnum = 11, ),_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0, .dq_label = "com.apple.root.user-initiated-qos", .dq_serialnum = 12, ),_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT, .dq_label = "com.apple.root.user-initiated-qos.overcommit", .dq_serialnum = 13, ),_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0, .dq_label = "com.apple.root.user-interactive-qos", .dq_serialnum = 14, ),_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT, .dq_label = "com.apple.root.user-interactive-qos.overcommit", .dq_serialnum = 15, ),};
Queue Priority | QOS | 说明 |
DISPATCH_QUEUE_PRIORITY_HIGH | QOS_CLASS_USER_INITIATED | 最高优先级 |
DISPATCH_QUEUE_PRIORITY_DEFAULT | QOS_CLASS_DEFAULT | 默认优先级 |
DISPATCH_QUEUE_PRIORITY_LOW | QOS_CLASS_UTILITY | 低优先级 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | QOS_CLASS_BACKGROUND | 后台优先级 |
NSLog(@"1、high %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); //2NSLog(@"2、OVERCOMMIT %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));NSLog(@"3、DEFAULT %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));//0NSLog(@"4、LOW %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); //-2NSLog(@"5、INITIATED %@", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 2));NSLog(@"6、BACKGROUND %@", dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 2));NSLog(@"7、DEFAULT %@", dispatch_get_global_queue(QOS_CLASS_DEFAULT, 2));NSLog(@"8、UTILITY %@", dispatch_get_global_queue(QOS_CLASS_UTILITY, 2));NSLog(@"9、%@", dispatch_get_main_queue());
QOS_CLASS_USER_INTERACTIVE:要求此类工作相对于系统上的其他工作以高优先级运行。指定这个QOS类是在几乎所有可用系统CPU和I/O带宽处于争用状态下运行的请求。这不是用于大型任务的节能QOS类。这个QOS类的使用应该仅限于与用户的关键交互,例如在主事件循环中处理事件、视图绘制、动画等。我们可以看出主线程对应的是QOS_CLASS_USER_INTERACTIVE。
QOS_CLASS_USER_INITIATED:这类工作的优先级要求低于关键的用户交互工作,但相对高于系统上的其他工作。这不是用于大型任务的节能QOS类。它的使用应该限制在足够短的持续时间内,这样用户在等待结果时就不太可能切换任务。典型的用户发起的工作将通过显示占位符内容或模态用户界面来指示进度。dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)获取的就是QOS_CLASS_USER_INITIATED的。
QOS_CLASS_DEFAULT:这类工作的优先级要求低于关键用户交互和用户发起的工作,但相对高于实用程序和后台任务。pthread_create()创建的线程如果没有指定QOS类的属性,则默认为QOS_CLASS_DEFAULT。qos_class是可以设置pthread优先级的。此QOS类值不打算用作工作分类,仅在传播或恢复系统提供的QOS类值时设置。我们用的比较多。
QOS_CLASS_UTILITY:这类工作的优先级要求低于关键用户交互和用户发起的工作,但相对高于低级系统维护任务。使用这个QOS类表明工作应该以一种能量和热效率的方式运行。效用工作的进展可能会或不会向用户显示,但这种工作的效果是用户可见的。对应的是我们常用的DISPATCH_QUEUE_PRIORITY_LOW。
QOS_CLASS_BACKGROUND:要求这些工作优先于其他工作运行。使用这个QOS类表明工作应该以最节能和最高效的方式运行。
我们一般获取全局队列都用方法dispatch_get_global_queue。两个参数。第一个参数是优先级的。和上面对应。所以qos_class和DISPATCH_QUEUE_PRIORITY_HIGH都可以使用。第二个参数是用来控制是否overcommit.
队列的执行
串行队列、并发队列、全局队列(并发)、主队列(串行)。
执行的方法有:同步执行、异步执行
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力(具备但是不一定非要开启新的线程根据实际情况决定)
1、串行队列,同步执行
// 1、串行队列,同步执行dispatch_queue_t test1Queue = dispatch_queue_create("com.liujilou.queue1", NULL);//默认串行 NULL 或 DISPATCH_QUEUE_SERIALfor (int a1=0; a1<10; a1++) {dispatch_sync(test1Queue, ^{
// 打印一下当前线程和 a1NSLog(@"%@ - %d",[NSThread currentThread],a1);});}NSLog(@"%@ 串行队列 同步执行",[NSThread currentThread]);/*结果2020-03-13 09:50:06.360033+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 02020-03-13 09:50:06.360210+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 12020-03-13 09:50:06.360365+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 22020-03-13 09:50:06.360524+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 32020-03-13 09:50:06.360644+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 42020-03-13 09:50:06.360807+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 52020-03-13 09:50:06.360917+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 62020-03-13 09:50:06.361140+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 72020-03-13 09:50:06.361400+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 82020-03-13 09:50:06.361552+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 92020-03-13 09:50:06.361826+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} 串行队列 同步执行
*/
在主线程执行,并且顺序执行,循环结束之后主线程的打印才输出。
2、串行队列,异步执行
// 2、串行队列,异步执行dispatch_queue_t test2Queue = dispatch_queue_create("com.liujilou.queue2", NULL);for (int a2=0; a2<10; a2++) {dispatch_async(test2Queue, ^{NSLog(@"%@ - %d",[NSThread currentThread],a2);});}NSLog(@"%@ 串行队列,异步执行",[NSThread currentThread]);NSLog(@"↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 分割线 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");/*结果2020-03-13 09:50:06.362828+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} 串行队列,异步执行2020-03-13 09:50:06.362855+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 02020-03-13 09:50:06.363099+0800 filedome[51520:1015947] ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 分割线 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑2020-03-13 09:50:06.364672+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 12020-03-13 09:50:06.364860+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 22020-03-13 09:50:06.366343+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 32020-03-13 09:50:06.382140+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 42020-03-13 09:50:06.382908+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 52020-03-13 09:50:06.383062+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 62020-03-13 09:50:06.383246+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 72020-03-13 09:50:06.383422+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 82020-03-13 09:50:06.383584+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 9*/
系统开辟了1条异步线程,且test2Queue 队列中的任务全部在开辟的线程中顺序执行。主线程的任务先后顺序不确定。一般情况下异步线程会有时间消耗,所以会先执行主线程的打印才会执行异步线程,如上打印出来的就是这样的一种情况,”串行队列,异步执行“这个先执行了,然后执行异步线程,然后中间又执行了”分割线“。
3、并发队列,同步执行
// 3、并发队列,同步执行dispatch_queue_t test3Queue = dispatch_queue_create("com.liujilou.queue3", DISPATCH_QUEUE_CONCURRENT);for (int a3=0; a3<10; a3++) {dispatch_sync(test3Queue, ^{NSLog(@"%@ - %d", [NSThread currentThread],a3);});}NSLog(@"%@ 并发队列,同步执行",[NSThread currentThread]);
/*2020-03-13 10:12:17.439909+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 02020-03-13 10:12:17.440074+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 12020-03-13 10:12:17.440198+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 22020-03-13 10:12:17.440302+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 32020-03-13 10:12:17.440422+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 42020-03-13 10:12:17.440549+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 52020-03-13 10:12:17.440663+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 62020-03-13 10:12:17.440798+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 72020-03-13 10:12:17.441149+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 82020-03-13 10:12:17.441387+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 92020-03-13 10:12:17.441713+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} 并发队列,同步执行*/
这和第1中情况一样。同步是按顺序执行,后面都要等待,不能开辟显得线程来执行,所以无论是串行还是并发队列只要是同步执行效果都一样。
4、并发队列,异步执行
// 4、并发队列,异步执行dispatch_queue_t test4Queue = dispatch_queue_create("com.liujilou.queue4", DISPATCH_QUEUE_CONCURRENT);for (int a4 =0; a4 <10; a4++) {dispatch_async(test4Queue, ^{NSLog(@"%@ - %d",[NSThread currentThread],a4);});}NSLog(@"%@ 并发队列,异步执行",[NSThread currentThread]);
/*2020-03-13 10:15:38.682348+0800 filedome[51971:1029504] <NSThread: 0x60000195c080>{number = 1, name = main} 并发队列,异步执行2020-03-13 10:15:38.682382+0800 filedome[51971:1029629] <NSThread: 0x600001921b80>{number = 3, name = (null)} - 02020-03-13 10:15:38.682387+0800 filedome[51971:1029626] <NSThread: 0x60000192d940>{number = 5, name = (null)} - 22020-03-13 10:15:38.682390+0800 filedome[51971:1029628] <NSThread: 0x6000019d9b40>{number = 4, name = (null)} - 32020-03-13 10:15:38.682401+0800 filedome[51971:1029627] <NSThread: 0x6000019d9a00>{number = 6, name = (null)} - 12020-03-13 10:15:38.682513+0800 filedome[51971:1029629] <NSThread: 0x600001921b80>{number = 3, name = (null)} - 42020-03-13 10:15:38.682524+0800 filedome[51971:1029626] <NSThread: 0x60000192d940>{number = 5, name = (null)} - 62020-03-13 10:15:38.682524+0800 filedome[51971:1029628] <NSThread: 0x6000019d9b40>{number = 4, name = (null)} - 52020-03-13 10:15:38.682585+0800 filedome[51971:1029627] <NSThread: 0x6000019d9a00>{number = 6, name = (null)} - 72020-03-13 10:15:38.682632+0800 filedome[51971:1029626] <NSThread: 0x60000192d940>{number = 5, name = (null)} - 82020-03-13 10:15:38.682677+0800 filedome[51971:1029629] <NSThread: 0x600001921b80>{number = 3, name = (null)} - 9开辟多条线程,无序执行。*/
开辟多条线程,无序执行。
5、主队列,同步执行 (死锁,循环等待)
// 5、主队列,同步执行 (死锁,循环等待)NSLog(@"打印5-1 %@ 之前的线程",[NSThread currentThread]);dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"打印5-2 %@ - sync",[NSThread currentThread]);});NSLog(@"打印5-3 %@ 之后的线程",[NSThread currentThread]);
会运行卡死,因为循环等待,主队列的东西需要等主线程执行完,而因为是同步执行不能开辟新的线程去执行,所以下面的的任务必须要等上面的任务执行完。
打印5-1 正常执行 - 打印5-2 在主队列需要等待主线程的打印5-3 执行完(等待) - 打印5-3 之前是同步执行,所以打印5-3 需要等同步执行的主队列内任务执行完(也就是打印5-2)(等待)。打印5-2 和 5-3 相互等待,造成循环等待。
6、主队列,异步执行
// 6、主队列,异步执行for (int a6=0; a6<10; a6++) {dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"%@ - %d",[NSThread currentThread],a6);});}NSLog(@"%@ sleep",[NSThread currentThread]);[NSThread sleepForTimeInterval:2.0];NSLog(@"%@ 主队列,异步执行",[NSThread currentThread]);
/*2020-03-13 10:33:49.743024+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} sleep2020-03-13 10:33:51.743730+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} 主队列,异步执行2020-03-13 10:33:51.756412+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 02020-03-13 10:33:51.756598+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 12020-03-13 10:33:51.756728+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 22020-03-13 10:33:51.756830+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 32020-03-13 10:33:51.756931+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 42020-03-13 10:33:51.757058+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 52020-03-13 10:33:51.757180+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 62020-03-13 10:33:51.757284+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 72020-03-13 10:33:51.757410+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 82020-03-13 10:33:51.757522+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 9*/
主线程休眠2秒之后再开始执行,循环会一直等待,主队列需要等主线程上的任务执行完才会执行。
7、同步任务的使用
// 7、同步任务的使用dispatch_queue_t test7Queue = dispatch_queue_create("com.liujilou.queue7", DISPATCH_QUEUE_CONCURRENT);
// 1、用户登录,必须要第一个执行dispatch_sync(test7Queue, ^{NSLog(@"%@ 用户登录",[NSThread currentThread]);});// 2、查询数据dispatch_async(test7Queue, ^{NSLog(@"%@ 查询数据",[NSThread currentThread]);});// 3、下载操作dispatch_async(test7Queue, ^{NSLog(@"%@ 下载",[NSThread currentThread]);});NSLog(@"%@ 同步任务的使用",[NSThread currentThread]);
/*2020-03-13 10:40:52.398542+0800 filedome[52376:1051709] <NSThread: 0x600002946580>{number = 1, name = main} 用户登录2020-03-13 10:40:52.398749+0800 filedome[52376:1051709] <NSThread: 0x600002946580>{number = 1, name = main} 同步任务的使用2020-03-13 10:40:52.398776+0800 filedome[52376:1051809] <NSThread: 0x600002936d00>{number = 3, name = (null)} 查询数据2020-03-13 10:40:52.398841+0800 filedome[52376:1051808] <NSThread: 0x6000029290c0>{number = 4, name = (null)} 下载*/
”用户登录”在主线程打印,后面两个在异步线程打印。“用户登录”必须放在第一位所以使用同步,无论等待多久都需要先执行完登录,后面的两个先后顺序不重要所以可以使用异步。
开发中,在对其有依赖的必须要先执行的任务,使用同步执行。对执行的顺序无所谓的使用异步执行。
8、block 异步任务包裹同步任务
// 8、block 异步任务包裹同步任务dispatch_queue_t test8Queue = dispatch_queue_create("com.liujilou.queue8", DISPATCH_QUEUE_CONCURRENT);void (^task)() = ^{
// 1、用户登录,必须要第一个执行dispatch_sync(test8Queue, ^{NSLog(@"%@ 用户登录",[NSThread currentThread]);});// 2、查询数据dispatch_async(test8Queue, ^{NSLog(@"%@ 查询数据",[NSThread currentThread]);});// 3、下载dispatch_async(test8Queue, ^{NSLog(@"%@ 下载",[NSThread currentThread]);});};dispatch_async(test8Queue, task);[NSThread sleepForTimeInterval:1.0];NSLog(@"%@ block 异步任务包裹同步任务",[NSThread currentThread]);
/*2020-03-13 10:57:54.509270+0800 filedome[52659:1066304] <NSThread: 0x600000149bc0>{number = 3, name = (null)} 用户登录2020-03-13 10:57:54.510370+0800 filedome[52659:1066304] <NSThread: 0x600000149bc0>{number = 3, name = (null)} 下载2020-03-13 10:57:54.510186+0800 filedome[52659:1066302] <NSThread: 0x60000017d700>{number = 4, name = (null)} 查询数据2020-03-13 10:57:55.509361+0800 filedome[52659:1066171] <NSThread: 0x6000001edbc0>{number = 1, name = main} block 异步任务包裹同步任务*/
因为整个block 是在异步执行的,所以即使里面“用户登录” 是同步执行,那也无法在主线程中执行,只能开一条异步线程执行,因为是同步的所以必须等他先执行,后面的两个“查询数据”、“下载”在同步执行完才会执行。
*/
9、全局队列(并发)
// 9、全局队列(并发)dispatch_queue_t test9Queue = dispatch_get_global_queue(0, 0);for (int a9=0; a9<10; a9++) {dispatch_async(test9Queue, ^{NSLog(@"%@ - %d",[NSThread currentThread],a9);});}[NSThread sleepForTimeInterval:1.0];NSLog(@"%@ 全局队列",[NSThread currentThread]);
/*2020-03-13 11:04:22.826343+0800 filedome[52760:1071918] <NSThread: 0x600001323c40>{number = 8, name = (null)} - 12020-03-13 11:04:22.826307+0800 filedome[52760:1071599] <NSThread: 0x600001331240>{number = 7, name = (null)} - 02020-03-13 11:04:22.826398+0800 filedome[52760:1071919] <NSThread: 0x600001323bc0>{number = 9, name = (null)} - 22020-03-13 11:04:22.826399+0800 filedome[52760:1071920] <NSThread: 0x600001330600>{number = 10, name = (null)} - 32020-03-13 11:04:22.826494+0800 filedome[52760:1071599] <NSThread: 0x600001331240>{number = 7, name = (null)} - 52020-03-13 11:04:22.826494+0800 filedome[52760:1071918] <NSThread: 0x600001323c40>{number = 8, name = (null)} - 42020-03-13 11:04:22.826518+0800 filedome[52760:1071920] <NSThread: 0x600001330600>{number = 10, name = (null)} - 62020-03-13 11:04:22.826543+0800 filedome[52760:1071919] <NSThread: 0x600001323bc0>{number = 9, name = (null)} - 72020-03-13 11:04:22.826602+0800 filedome[52760:1071599] <NSThread: 0x600001331240>{number = 7, name = (null)} - 82020-03-13 11:04:22.826675+0800 filedome[52760:1071918] <NSThread: 0x600001323c40>{number = 8, name = (null)} - 92020-03-13 11:04:23.826701+0800 filedome[52760:1071423] <NSThread: 0x600001349ec0>{number = 1, name = main} 全局队列参数:“调度优先级“ 和 “服务质量”*/
参数:“调度优先级“ 和 “服务质量”
小结:
开不开线程:同步不开,异步开
开几条线程:串行开一条,并发开多条(异步)
主队列:专门用来在主线程上调度任务的”队列“,主队列不能再其他线程中调度任务! 同步 + 主队列 就会 死锁
如果主线程上当前正在有执行的任务,主队列暂时不会调度任务的执行!主队列同步任务,会造成死锁。
同步任务可以队列调度多个异步任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这是依赖关系。
全局队列:并发,能够调度多个线程,执行效率高,但是相对费点。串行队列效率低,省电省流量。或者是任务之间需要依赖也可以使用串行队列。
二、一些常用方法介绍
1、GCD 的一次性代码
整个程序运行过程中只会执行一次 + 本身是线程安全的
应用:单例模式
内部原理:通过判断 onceToken 的值来决定是否执行block 中的任务,只有在第一次为0,其他都为-1
+(instancetype)sharedInstance{static MBDB *_instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{if (_instance == nil) {_instance = [[MBDB alloc] init];}});return _instance;
}
2、GCD的延迟执行
@param when#> 设置时间(CGD中的时间单位为纳秒) description#>
@param queue#> 队列(决定block块红的任务在哪个线程中执行,如果主队列就在主线程,否则在子线程) description#>
@param void 执行的任务
原理:先等2秒,然后在吧任务提交到队列,如果先提交到队列,那么任务不好控制,并且很好队列的资源
NSLog(@"gcd 1");dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (ino64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"gcd 2");});
3、GCD的快速迭代:
将一个文件夹下多张图片剪切到另外一个文件夹下
// 快速迭代(遍历)for (int i=0; i<10; i++) {NSLog(@"%@ - %d",[NSThread currentThread],i);}
快速迭代 -- 会开启多条子线程和主线程一起并发的执行任务
iteration 遍历的次数
queue 队列 -- 如果是主队列则会死锁,如果是串行队列则跟for 循环一样
size_t 索引
dispatch_queue_t queue3 = dispatch_get_global_queue(0, 0);dispatch_apply(10, queue3, ^(size_t i) {NSLog(@"%@ - %zd",[NSThread currentThread],i);});
/*2020-03-13 14:29:20.838552+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 02020-03-13 14:29:20.838714+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 12020-03-13 14:29:20.838850+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 22020-03-13 14:29:20.838951+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 32020-03-13 14:29:20.839059+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 42020-03-13 14:29:20.839612+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 52020-03-13 14:29:20.840336+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 62020-03-13 14:29:20.840730+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 72020-03-13 14:29:20.841434+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 82020-03-13 14:29:20.842253+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 9*/
4、GCD栅栏函数
需求:有一个新任务打印 00000 的任务,要求在 1 2 执行完成之后执行,要保证该任务执行完成之后才能执行后面的3、4任务。
讲解:栅栏前面的任务并发执行,后面的任务也是并发执行,当前面的任务执行完之后执行栅栏函数中的任务,等该任务执行完毕后在执行后面的任务。
警告:不能使用全局并发队列 (dispatch_get_global_queue(0, 0)),否者不能拦截
//1、创建队列dispatch_queue_t queue4 = dispatch_queue_create("com.liujilou.queue2-4", DISPATCH_QUEUE_CONCURRENT);//2、封装任务,并且添加到队列dispatch_async(queue4, ^{NSLog(@"%@ - 打印1",[NSThread currentThread]);});dispatch_async(queue4, ^{NSLog(@"%@ - 打印2",[NSThread currentThread]);});//栅栏函数dispatch_barrier_async(queue4, ^{NSLog(@"00000");});dispatch_async(queue4, ^{NSLog(@"%@ - 打印3",[NSThread currentThread]);});dispatch_async(queue4, ^{NSLog(@"%@ - 打印4",[NSThread currentThread]);});
/*2020-03-13 14:40:37.109550+0800 filedome[54765:1144990] <NSThread: 0x60000392da80>{number = 3, name = (null)} - 打印12020-03-13 14:40:37.109550+0800 filedome[54765:1145144] <NSThread: 0x600003928340>{number = 4, name = (null)} - 打印22020-03-13 14:40:37.109760+0800 filedome[54765:1145144] 000002020-03-13 14:40:37.109933+0800 filedome[54765:1144990] <NSThread: 0x60000392da80>{number = 3, name = (null)} - 打印32020-03-13 14:40:37.109999+0800 filedome[54765:1145144] <NSThread: 0x600003928340>{number = 4, name = (null)} - 打印4因为是异步执行,所以1、2是无序的 3、4是无序的,但是3、4的打印必须在栅栏之后执行。*/
5、GCD的队列组
控制多条队列中任务都完成在执行新的,可用在下载多张小图片,然后都下载完成后拼接起来
需求:有5个任务,在多个队列的子线程中并发执行,添加 打印00000 的任务,必须在所有任务完成之后再执行
增加需求:拦截多个队列中的任务
dispatch_group_t group5 = dispatch_group_create();dispatch_queue_t queue51 = dispatch_queue_create("com.liujilou.queue2-5-1", DISPATCH_QUEUE_CONCURRENT);dispatch_queue_t queue52 = dispatch_queue_create("com.liujilou.queue2-5-2", DISPATCH_QUEUE_CONCURRENT);dispatch_group_async(group5, queue51, ^{NSLog(@"%@ - 打印1",[NSThread currentThread]);});dispatch_group_async(group5, queue51, ^{NSLog(@"%@ - 打印2",[NSThread currentThread]);});dispatch_group_async(group5, queue51, ^{NSLog(@"%@ - 打印3",[NSThread currentThread]);});dispatch_group_async(group5, queue52, ^{NSLog(@"%@ - 打印4",[NSThread currentThread]);});dispatch_group_async(group5, queue52, ^{NSLog(@"%@ - 打印5",[NSThread currentThread]);});
// 拦截通知,当所有的任务都执行完毕然后打印 00000
// 注意:通知中的第二个参数能控制执行最后的任务在子线程 还是主线程dispatch_group_notify(group5, dispatch_get_main_queue(), ^{NSLog(@"%@ - 00000",[NSThread currentThread]);});
/*2020-03-13 15:23:05.146160+0800 filedome[55364:1166080] <NSThread: 0x600000bb1040>{number = 4, name = (null)} - 打印32020-03-13 15:23:05.146176+0800 filedome[55364:1166079] <NSThread: 0x600000bb0f40>{number = 6, name = (null)} - 打印42020-03-13 15:23:05.146231+0800 filedome[55364:1166077] <NSThread: 0x600000bb42c0>{number = 5, name = (null)} - 打印22020-03-13 15:23:05.146496+0800 filedome[55364:1166080] <NSThread: 0x600000bb1040>{number = 4, name = (null)} - 打印52020-03-13 15:23:05.147005+0800 filedome[55364:1166078] <NSThread: 0x600000bb1080>{number = 3, name = (null)} - 打印12020-03-13 15:23:05.165266+0800 filedome[55364:1166018] <NSThread: 0x600000bc2880>{number = 1, name = main} - 00000无论是使用一个队列还是多个队列去执行,只要是在一个队列组里都是需要等全部执行完才回去通知主线程(当然也可以用一个子线程)。*/
18、iOS底层分析 - GCD(一)基本概念与简单使用相关推荐
- 视频教程-iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-iOS
iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化 小码哥教育CEO,曾开发了2个iOS的流行开源框架(MJRefresh.MJExtension),目前在国内的使用率非常高. 李 ...
- iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-李明杰-专题视频课程...
iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-236人已学习 课程介绍 得遇名师,突飞猛进!iOS培训王者MJ(李明杰)老师精心研发,iOS进阶课程,实用技术 ...
- iOS底层探索(二) - 写给小白看的Clang编译过程原理
iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...
- String的底层分析 (学习笔记)
StringTable底层分析 String的基本特性 StringPool String的内存分配 字符串的拼接操作 拼接效率的对比 intern()的理解 new String("&qu ...
- iOS底层原理探究 第一探. 事件传递和响应者链
一. 声明: 本文意在探讨, 也参考了几位大神的文章, 在最后我会把链接发出来, 如果有理解错误的地方, 请大神们指正哈! 二. 前言: 最近自己做项目的时候, 用到了UITabbarContro ...
- iOS底层原理之内存管理
文章目录 定时器 CADisplayLink.NSTimer GCD定时器 内存管理 iOS程序的内存布局 Tagged Pointer OC对象的内存管理 拷贝 引用计数的存储 dealloc 自动 ...
- iOS底层开发消息发送与转发流程
iOS底层开发消息转发流程 一,cache缓存读取流程分析 首先我们上一章已经了解到对应的cache_t的数据结构 _bucketsAndMaybeMask:指针类型,存放buckets的首地址 _m ...
- ①、iOS-RAC的开发用法-底层分析以及总结
iOS RAC系列 ①.iOS-RAC的开发用法-底层分析以及总结 ②.iOS-RAC-核心类分析-RACPassthroughSubscriber订阅者-RACScheduler调度者-RACDis ...
- iOS 多线程和GCD(Grand Central Dispath) 教程 (一)
iOS 多线程和GCD(Grand Central Dispath) 教程 (一) 本文翻译自 Ray Wenderlich 的博客 点击打开原文链接.全部由本人亲手翻译...童叟无欺~ 你有木有遇 ...
最新文章
- 入门单片机选择51还是stm32?入门单片机有哪些好的教学视频?
- java 二叉树_二叉树实现java
- 安全专家呼吁希拉里要求重新计票
- 【Spring学习】Spring JdbcTemplate之五类方法总结
- Python程序开发——第五章 函数
- MySQL事务控制语句
- dotcpp1115 DNA-打印图案
- 为JPA的本机查询API键入安全查询
- 内存溢出和内存泄漏的区别,产生原因以及解决方案
- pycharm终端运行python文件_在PyCharm终端中执行python manage.py..._慕课问答
- 第十章 提升论文的可读性 --《英语科技写作(文法与修辞原则)》by 方克涛
- 当贝显示服务器生病,【当贝市场】电视盒子卡顿的三大原因
- 分享typecho博客的Next主题包
- 软件测评师的一些重点①
- 聚合数据手机话费充值API,话费充值功能接入
- 百万并发下的Nginx优化,看这一篇就够了!
- 什 么 是 可 重 入 性 , 为 什 么 说 Synchronized 是 可 重 入 锁 ?
- ios QQ登录已经安装QQ客户端但仍然提示下载QQ,您没有安装最新版本qq
- windows 通过 Xshell 传文件到 Linux
- 笔记本电脑设备管理器中'符合USBxHCI的主机控制器'出现感叹号,所有的USB接口失灵--解决
热门文章
- 机器人笔记本清灰_小熏的编程日记 » 愉悦的小机器人调教经历(一):使用笔记本为HTC G4提供无线路由...
- ROS_IP选用WIFI(无线局域网)网段,当wifi连接断开,出现Master崩溃
- 2018王鼎杯-fakebook-详细解说
- OpenCV各种版本官方下载
- 自相关函数和自协方差函数
- 小梅哥FPGA学习笔记——开发流程及仿真示例
- perl中our的用法
- java线程设计模式_JAVA多线程设计模式
- illumina 双末端测序
- 《流浪地球》让刘慈欣赚了多少钱?技术男搞写作原来这么简单