上篇文章中介绍了 pthread 和 NSThread 两种多线程的方式,本文将继续介绍 GCD 和 NSOperation 这两种方式。。
1.GCD
1.1 什么是GCD
- GCD 全称 Grand Central Dispatch,可译为“牛逼的中枢调度器”
- GCD 基于纯 C 语言,内部封装了强大的函数库
1.2 使用 GCD 有什么优势
- GCD 是苹果公司为多核的并行运算提出的解决方案
- GCD 会自动利用更多的CPU内核 (如 二核 ,四核)
- GCD 会自动管理线程的生命周期(创建 、 调度 、 销毁线程)
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
1.3 GCD 的使用
GCD 有两个核心的概念
- 任务 : 需要执行的操作
- 队列 : 用来存放任务
GCD 的使用步骤
- 制定任务
- 将任务放入到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行,队列中的任务取出遵循 FIFO原则。(FIFO:先进先出,队列原则)
GCD 中有两个用来执行任务的常用函数
同步方法执行任务
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)queue : 队列Block : 任务异步方法执行任务
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
同步和异步的区别
- 同步 : 只能在当前的线程中执行任务,不具备开启新线程的能力
- 异步 : 可以在新的线程中执行任务,具备开启新线程的能力
1.4 队列的类型
GCD 的队列可以分为 2 大类
- 并发队列 ( Concurrent Dispatch Queue )
- 可以让多任务并发执行,自动开启多个线程同时执行任务
- 并发功能只有在异步(dispatch_async)函数下才有效
- 串行队列 ( Serial Dispatch Queue )
- 让任务一个接一个地有序执行(一个任务执行完毕后才开始执行下一个)
注意:同步 、 异步、并发、串行的区分
同步
和异步
主要影响: 能不能开启新的线程- 同步 : 只是在当前线程中执行任务 ,不具备开启新线程的能力
- 异步 : 可以在新的线程中执行任务,具备开启新县城的能力
并发
和串行
主要影响: 任务的执行方式- 并发 : 多个任务并发执行
- 串行 : 多个任务一次顺序执行
1.5 GCD 的各种队列的组合
- 异步函数 + 并发队列:可以同时开启多条线程
|
- 同步函数 + 并发队列:不会开启新的线程
|
- 异步函数 + 串行队列:会开启新的线程,但是任务是串行的,执行完一个任务,再执行下一个任务
|
- 异步函数 + 主队列:只在主线程中执行任务
|
- 同步函数 + 主队列:
|
各种队列的执行效果 :
注意:
使用 sync 函数往当前串行队列中添加任务,会卡住当前的串行队列
1.6 GCD 个线程之间通信
通常开辟子线程是为了执行耗时操作。如下载图片的等,使用 GCD 进行线程间通信非常方便,示例代码如下:
|
1.7 GCD 其他常用函数
- 阻隔执行任务的函数
|
- 延迟执行
|
- 一次性函数
|
- 快速迭代函数(遍历)
|
- GCD 队列组
|
2. 使用 GCD 实现单例
2.1 单例模式
单例模式是开发过程中长期积累的一种编程习惯。
单例模式作用如下:
- 可以保证在程序运行过程中,一个类只有一个实例,而且该实例易于供外界访问
- 方便控制实例的个数,节约系统资源
单例模式使用场合:
- 在整个应用中,共享一份资源(该资源只需要创建初始化1次,如Application,NSUserDefault 等)
2.2 单例模式的实现(纯代码)
- 在 .m 中保留一个全局的 static 实例
|
- 重写 allocWithZone: 方法,创建唯一实例
|
- 提供类方法,供外界使用
|
- 实现 copyWithZone: 方法
|
2.3 单例模式的实现(宏)
从上面的实现中可以看到,单例的实现方式是一样的,我们可以把它抽取成一个宏来实现,这样更加方便使用.
如下是单例的宏实现,只需在对应的单例类中添加两个对应的宏,就可轻松实现单例。
|
思考:为什么不使用继承?
|
3. NSOperation
3.1 NSOperation 简介
NSOperation 是 OS X 和 iOS 开发中最后一种多线程实现方式,它是基于 GCD 的 OC 封装,使用更加面向对象。
- NSOperation 的作用
- 配合使用NSOperation 和 NSOperationQueue 实现多线程
- NSOperation 和 NSOperationQueue 实现多线程的具体步骤
- 先将需要执行的操作封装到一个 NSOperation 对象中
- 然后将 NSOperation 对象添加到 NSOperationQueue 中
- 系统会自动将 NSOperationQueue 中的 NSOperation 取出来,并将封装的操作放到一条新线程中执行
3.2 NSOperation 的子类
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperation子类的方式有3种
- NSInvocationOperation
- NSBlockOperation
- 自定义子类继承NSOperation,实现内部相应的方法
NSInvocationOperation
- 创建NSInvocationOperation对象
|
- 调用start方法开始执行操作
|
注意
- 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
- 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
NSBlockOperation
- 创建NSBlockOperation对象
|
- 通过addExecutionBlock:方法添加更多的操作
|
注意:
只要NSBlockOperation封装的操作数 > 1,就会异步执行操作
3.3 NSOperationQueue
NSOperationQueue的作用
- NSOperation可以调用start方法来执行任务,但默认是同步执行的
- 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中
|
3.4 最大并发数
什么是并发数?
- 同时执行的任务数
- 比如,同时开3个线程执行3个任务,并发数就是3
最大并发数的相关方法
|
3.5 队列的取消、暂停、恢复
- 取消队列的所有操作
|
提示:也可以调用NSOperation的- (void)cancel方法取消单个操作
- 暂停和恢复队列
|
3.6 操作依赖
- NSOperation之间可以设置依赖来保证执行顺序
- 比如一定要让操作A执行完后,才能执行操作B,可以这么写
|
- 可以在不同queue的NSOperation之间创建依赖关系(如图)
注意:
不能相互依赖,比如A依赖B,B依赖A
3.7 操作的监听
可以监听一个操作的执行完毕
|
3.8 自定义NSOperation
自定义NSOperation的步骤很简单
- 重写
- (void)main
方法,在里面实现想执行的任务 - 重写
- (void)main
方法的注意点- 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
- 经常通过
- (BOOL)isCancelled
方法检测操作是否被取消,对取消做出响应
|
3.9 NSOperation 线程间通信
此处依旧以下载并合成一张图片为例,只需开启两个子线程分别下载image,第三个线程为合并操作, 然后添加线程依赖。并放到队列中
|
简单的,只有下载图片然后放到主线程展示的线程通信如下:
|
4 小结
本文主要讲解了 GCD 和 NSOperation 两种多线程的创建和使用方式。加上上篇文章 共有 pthread 、 NSThread 、 GCD 和 NSOperation 四种多线程方案,实际使用中需要根据项目需求灵活使用。