NSOperation
和NSOperationQueue
配合使用也能实现多线程编程。
一、操作
NSOperation
是对GCD封装的抽象类,使用起来更加面向对象。但是它不具备封装操作的能力,必须使用它的子类才可以。
系统为我们提供了两个NSOperation
的子类:
NSInvocationOperation
:把任务放到了函数中。NSBlockOperation
:把任务放到block
块中。
我们也可以自定义继承自NSOperation
的子类实现内部相应的方法。
1.1. 实现步骤
NSOperation
和NSOperationQueue
实现多线程的具体步骤:
- 现将需要执行的操作封装到一个
NSOperation
对象中; - 然后将
NSOperation
对象添加到NSOperationQueue
中; - 系统会自动将
NSOperationQueue
中的NSOperation
取出,并把其封装的操作放到一条新线程中执行。
1.2. 封装操作
1.2.1. NSInvocationOperation
1 | - (void)invocationOperation { |
1.2.2. NSBlockOperation
1 | - (void)blockOperation { |
NSBlockOperation
可以通过addExecutionBlock:
方法增加任务:
1 | [operationB addExecutionBlock:^{ |
当一个操作中的任务数量大于1的时候,就会开启子线程和主线程一起执行任务。
1.2.3. 自定义Operation
自定义Operation
时,自定义类只有通过重写main
方法封装任务。因为队列调用addOperation:
执行任务时,会先调用操作的start
方法,而start
方法会调用main
方法。
示例代码:
1 | // DBOperation.h |
二、队列
操作队列分为自定义队列和主队列。
- 自定义队列:
[[NSOperationQueue alloc] init]
,并发队列(但是可以通过控制并发数让其成为串行队列) - 主队列:
[NSOperationQueue mainQueue]
,串行队列,和主线程相关。
2.1. 基本使用
示例代码(NSInvocationOperation
):
1 | - (void)invocationOperationWithQueue { |
结论:操作不需要手动开启,队列会自动开启操作并执行任务。并且任务是在子线程中执行的。
示例代码():
1 | - (void)blockOperationWithQueue { |
结论和NSInvocationOperation
一样。
队列添加操作还有一种简洁的写法:
1 | [queue addOperationWithBlock:^{ |
2.2. 最大并发数
队列属性maxConcurrentOperationCount
可以设置最大并发数(同一时间有多少线程在执行)。
默认值是-1,代表不受限制。
1 | static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1; |
如果设置为0,代表不执行任务。
设置最大并发数对于任务数量大于1的操作是无效的(针对的是追加任务)。当操作中任务数量大于1时,会开启多条子线程和当前线程一起工作。
1 | - (void)blockOperationWithQueue { |
建议开发中使用
NSBlockOperation
或者自定义操作,因为在Swift中不能使用NSInvocationOperation
。
2.3. 暂停和取消
队列可以设置暂停、继续、取消。
示例代码:
1 | // 开始 |
注意:只能暂停/取消还未开始执行的操作,正在执行任务的操作不可分割,必须执行完毕。
2.4. 依赖和监听
2.4.1. 依赖
操作依赖非常简单,只需要设置addDependency:
即可。
示例代码:
1 | - (void)operationDependency { |
依赖是可以跨队列的,比如operationA
在队列queueA
中,operationB
在队列queueB
中,可以设置[operationA addDependency:operationB]
,等待operationB
执行完成后再执行operationA
。
注意:必须在被添加到队列前设置依赖。不能设置循环依赖,否则产生循环的任务不会执行。
2.4.2. 监听
执行操作的完成回调函数completionBlock
就可以监听到操作任务是否执行完毕。
1 | operationC.completionBlock = ^{ |
2.5. 线程间通信
使用[NSOperationQueue mainQueue]
就可以获取到主队列,addOperationWithBlock
添加的任务是在主线程中执行的。
示例代码:
1 | - (void)downloadImage { |
三、GCD和NSOperation的对比
- GCD是纯C语言的API,而操作队列则是OC对象。
- 在GCD中,任务用块(
block
)来表示,而块是个轻量级的数据结构。相反操作队列中的NSOperation
则是个更加轻量级的OC对象。 NSOperation
可以方便的调用cancel
方法来取消某个操作,而GCD中的任务是无法被取消的。NSOperation
可以方便的指定操作间的依赖关系。NSOperation
可以通过KVO提供对NSOperation
对象的精细控制(如监听当前操作是否被取消或是否已经完成等)。NSOperation
可以方便的指定操作优先级。- 通过自定义
NSOperation
的子类可以实现操作重用(重写main
方法)。 - 具体使用GCD还是
NSOperation
应看具体的情况(一般情况下,复杂业务使用NSOperation
,简单业务使用GCD)。