iOS多线程的方法有3种:html
其中,由苹果所倡导的为多核的并行运算提出的解决方案:GCD可以访问线程池,而且可在应用的整个生命的周期里面使用,通常来讲,GCD会尽可能维护一些适合机器体系结构的线程,在有工做需求的时候,自动利用更多的处理器核心,以此来充分使用更强大的机器系统性能。在之前,iOS设备为单核处理器的,线程池的用处并不大,可是如今的移动设备,包括iOS设备,愈发地朝多核的方向迈进,所以GCD中的线程池,可以在此类设备中,可以使得强大的硬件系统性能上获得更加完善的利用。ios
GCD,无疑是最便捷的,基于C
语言的所设计的。在使用GCD的过程当中
,最方便的,莫过于不须要编写基础线程代码,其生命周期也不须要手动管理;建立须要的任务,而后添加到已建立好的queue队列,GCD便
会负责建立线程和调度任务,由系统直接提供线程管理。数据结构
这样一种多线程的方式,咱们也会在实际项目中常常看到:app中,因为数据的执行与交换所消耗的时间长,致使须要反馈给用户UI界面每每出现延迟的现象。这样咱们能够经过多线程的方法,让须要调用的方法在后台执行、在主线程上进行UI界面的切换,这样不只是用户体验更加友好美观,也使得程序设计井井有理。多线程
本文主要粗略介绍GCD的通常使用,以及GCD中dispatch_前缀方法调用的做用和使用范围。并发
UI界面以下图,经过建立4个按钮事件,分析4种不一样的函数所执行的程序块运行方式:app
【本次开发环境: Xcode:7.2 iOS Simulator:iphone6 By:啊左】 iphone
1、GCD的使用异步
GCD对于开发者来讲,最简单的,就是经过调用dispatch把一连串的异步任务添加到队列中,进行异步执行操做。async
代码调用以下:函数
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async(异步)与
sync(同步):
固然,咱们也可使用同步任务,使用dispatch_sync
函数添加到相应的队列中,而这个函数会阻塞当前调用线程,直到相应任务完成执行。
可是,也正由于这样的同步特性,在实际项目中,当有同步任务添加到正在执行同步任务的队列时,串行的队列会出现死锁。并且因为同步任务会阻塞主线程的运行,可能会致使某个事件没法响应。
队列(queue):
须要注意的是,调用dispatch_async不会让块运行,而是把块添加到队列末尾。队列不是线程,它的做用是组织块。(若是读者学过数据结构的知识,就会知道队列的基本特征如饭堂排队队,先到的排前面,先打到饭,也就是“先进先出”原理)
在GCD中,能够给开发者调用的常见公共队列有如下两种:
dispatch_get_global_queue
:用于获取应用全局共享的并发队列 (提供多个线程来执行任务,因此能够按序启动多个任务并发执行。可用于后台执行任务)dispatch_get_main_queue
: 用于获取应用主线程关联的串行调度队列(只提供一个线程执行任务。运行的main主线程,通常用于UI的搭建)(还有另一种,dispatch_get_current_queue,
用于获取当前正在执行任务的队列,主要用于调试,可是
在iOS 6.0
以后苹果已经废弃,缘由是容易形成死锁。详情能够查看官方注释。)
这两种公共队列的调用即可以解决咱们刚刚关于后台执行任务、主线程用于更新UI界面的问题,
结构以下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 把逻辑计算等须要消耗长时间的任务,放在此处的全局共享的并发队列执行;
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程更新UI界面;
}); });
例如在有一些项目中,会涉及到异步下载图片,这个时候就可使用这样一种结构来进行任务的分配:
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //先把下载数据的任务放在全局共享并发队列中执行
NSURL *url = [NSURL URLWithString:@"图片的URL"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; if(data != nil) { // 完成后,回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); } });
2、 串行队列 and 并行队列
1.串行(Serial)的执行:指同一时间每次只能执行一个任务。 线程池只提供一个线程用来执行任务,因此后一个任务必须等到前一个任务执行结束才能开始。
能够添加多个任务到队列中,执行次序FIFO,可是当程序须要执行大量的任务时,虽然系统容许,可是鉴于程序的资源分配,应该交给全局并发队列来完成才能更好地发挥系统性能。
建立串行队列的方式以下:
dispatch_queue_t serialQueue = dispatch_queue_create("zuoA", NULL); //第一个参数是队列的名称,一般使用公司的反域名;第二个参数是队列相关属性,通常用NULL.
关于什么是FIFO次序,咱们用代码解释一下
- (IBAction)SerialQueue:(UIButton *)sender { dispatch_queue_t serialQueue = dispatch_queue_create("zuoA", NULL); dispatch_async(serialQueue, ^{ sleep(3); NSLog(@"A任务"); }); dispatch_async(serialQueue, ^{ sleep(2); NSLog(@"B任务"); }); dispatch_async(serialQueue, ^{ sleep(1); NSLog(@"C任务"); }); }
console控制台显示以下:
2016-03-15 15:04:11.909 dispatch_queue的多任务GCD使用[92316:2538875] A任务
2016-03-15 15:04:13.910 dispatch_queue的多任务GCD使用[92316:2538875] B任务
2016-03-15 15:04:14.910 dispatch_queue的多任务GCD使用[92316:2538875] C任务
能够看获得,即便须要等待几秒,后面所添加的任务也必须等待前面的任务完成后才能执行,相似咱们前面所讲"饭堂"排队的例子,队列彻底按照"先进先出"的顺序,也便是所执行的顺序取决于:开发者将工做任务添加进队列的顺序。
2.并行(concurrent)的执行:可同一时间能够同时执行多个任务。
因此,与串行的不一样的是,虽然启动时间一致,可是这是“并发执行”,所以不须要等到上一个任务完成后才进行下一个任务。因此每一个块中的各部分的前后执行的顺序须要视状况而定。
上代码,找不一样。。。
- (IBAction)concurrentQueue:(UIButton *)sender { dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(concurrentQueue, ^{ sleep(3); NSLog(@"A任务"); }); dispatch_async(concurrentQueue, ^{ sleep(2); NSLog(@"B任务"); }); dispatch_async(concurrentQueue, ^{ sleep(1); NSLog(@"C任务"); }); }
console控制台显示以下:
2016-03-15 15:02:06.911 dispatch_queue的多任务GCD使用[92294:2537296] C任务 2016-03-15 15:02:07.907 dispatch_queue的多任务GCD使用[92294:2537147] B任务 2016-03-15 15:02:08.908 dispatch_queue的多任务GCD使用[92294:2537177] A任务
经过控制台左边的时间记录,能够看到,与串行队列不一样的是,并行队列中这3个任务的并行启用,与串行不一样的是,不须要等到A任务调用完,就已经在调用B、C,显著地提升了线程的执行速度,凸显了并行队列所执行的异步操做的并行特性;
另外,从这段代码中,不一样的是串行队列须要建立一个新的队列,而并行队列中,只须要调用iOS系统中为咱们提供的全局共享dispatch_get_global_queue就能够了:
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数为iOS系统为全局共享队列提供4种调度的方式,主要区别便是优先级的不一样而已:
咱们采用默认的DISPATCH_QUEUE_PRIORITY_DEFAULT方式,而右边的第二个参数是苹果预留的,暂时没有其余的含义,因此,通常默认为:0。
并发的好处就是不须要像串行同样按照顺序执行,并发执行能够显著地提升速度。
3、dispatch_group_async的使用
有时候,咱们会遇到这样的状况,UI界面部分的显示,须要在完成几个任务再进行主任务,例如3张图片下载完毕,才通知UI界面已经完成任务。
咱们能够经过分派组(dispatch group)进行并发程序块分配的运用,将异步分派(dispatch_async)的全部程序块设置为松散,或者分配给多个线程来执行,监听到这组任务所有完成后,使用dispatch_group_notify()通知并调用notify中的块,例如UI界面的程序块。
代码:
- (IBAction)groupQueue:(UIButton *)sender { 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, ^{ sleep(3); NSLog(@"A任务"); }); dispatch_group_async(group, queue, ^{ sleep(2); NSLog(@"B任务"); }); //group组中的任务完后,通知并调用notify中的块
dispatch_group_notify(group, queue, ^{ NSLog(@"主任务"); }); }
console控制台显示以下:
2016-03-16 11:18:41.306 dispatch_queue的多任务GCD使用[94865:2718342] B任务
2016-03-16 11:18:42.302 dispatch_queue的多任务GCD使用[94865:2718341] A任务
2016-03-16 11:18:42.303 dispatch_queue的多任务GCD使用[94865:2718341] 主任务
结果验证了前面说的,直到分派组任务都完后,notify添加的任务块才会执行。
眼尖的读者可能也发现,整个任务组完成的时间比2个任务分别运行的时间还要短!这得益于咱们同时进行了两种计算~
固然在真实的开发运用中,这种明显运行时间缩短的效果,取决于所须要执行的工做量和可用的资源,以及多个CPU核心的可用性,所以在多核技术日益完善的大环境下,这样一种多线程技术将获得更有效的利用。
4、dispatch_barrier_async的使用
dispatch_barrier(分派屏障)是当前面的任务执行完后,才执行barrier块的任务,并且后面的任务也得等到barrier块的执行完毕后才能开始执行。
很好地突显了“障碍物”这样的特性,那么代码上应该怎么写呢?
按照并发的性质,咱们在barrierQueue方法中敲入如下代码:
- (IBAction)barrierQueue:(UIButton *)sender { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ sleep(2); NSLog(@"A任务"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"B任务"); }); dispatch_barrier_async(queue, ^{ NSLog(@"barrier任务"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"C任务"); }); }
console控制台显示以下:
2016-03-16 13:18:47.525 dispatch_queue的多任务GCD使用[95191:2752854] barrier任务
2016-03-16 13:18:48.529 dispatch_queue的多任务GCD使用[95191:2752839] B任务
2016-03-16 13:18:48.529 dispatch_queue的多任务GCD使用[95191:2752844] C任务
2016-03-16 13:18:49.528 dispatch_queue的多任务GCD使用[95191:2752840] A任务
任务的执行顺序依然是跟并行队列的方法同样,barrier没有发挥它的“障碍物”的界限做用。这是由于barrier这一块是依赖队列queue的模型来执行的,当队列为全局共享时,barrier就没法发挥其做用。咱们须要新建立一个队列,
dispatch_queue_t queue = dispatch_queue_create("zuoA", DISPATCH_QUEUE_SERIAL);
完整的程序以下:
- (IBAction)barrierQueue:(UIButton *)sender { dispatch_queue_t queue = dispatch_queue_create("zuoA", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ sleep(2); NSLog(@"A任务"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"B任务"); }); dispatch_barrier_async(queue, ^{ NSLog(@"barrier任务"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"C任务"); }); }
console控制台显示以下:
2016-03-16 13:30:14.251 dispatch_queue的多任务GCD使用[95263:2759658] A任务
2016-03-16 13:30:15.255 dispatch_queue的多任务GCD使用[95263:2759658] B任务
2016-03-16 13:30:15.255 dispatch_queue的多任务GCD使用[95263:2759658] barrier任务
2016-03-16 13:30:16.256 dispatch_queue的多任务GCD使用[95263:2759658] C任务
这就是咱们想要获得的效果:确实只有在前面A、B任务完成后,barrier任务才能执行,最后才能执行C任务。
那么,dispatch_queue_create为何要用 DISPATCH_QUEUE_SERIAL,能够用其余么?答案是确定的。把参数换成DISPATCH_QUEUE_CONCURRENT
能够获得如下输出:
2016-03-16 13:34:23.855 dispatch_queue的多任务GCD使用[95294:2762604] B任务 2016-03-16 13:34:24.853 dispatch_queue的多任务GCD使用[95294:2762603] A任务 2016-03-16 13:34:24.853 dispatch_queue的多任务GCD使用[95294:2762603] barrier任务 2016-03-16 13:34:25.856 dispatch_queue的多任务GCD使用[95294:2762603] C任务
也就是说,A、B、C任务彻底是按照队列的顺序执行,只是因为barrier块的“屏障”做用,把A、B任务放在前面,而使得后来加入的C任务只有等到barrier块执行完毕才能运行;
5、dispatch_suspend(暂停)和 dispatch_resume(继续)
queue
的引用计数增长;dispatch_resume(queue),此时queue启动执行块的操做,queue
的引用计数减小;
须要注意的是,suspend与resume是异步的,只在block块之间调用,并且必须是成对存在的。
还有一些其余的dispatch函数,例如
dispatch_once:可使特定的块在整个应用程序生命周期中只被执行一次~(在单例模式中使用到.)
dispatch_apply:执行某个代码片断n次(开发者能够本身设定)。
dispatch_after:当咱们须要等待几秒后进行某个操做,可使用这个函数;
注意事项:
1.在上面的例子中,咱们没有使用过手动内管其内存,由于系统会自动管理。
若是你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8