基础概念
根据文章iOS多线程之NSOperation和苹果官方文档Class NSOperation可以得知以下概念
- NSOperationQueue: 本身是个队列+线程池,可以执行一系列的函数
- NSOperation: 本身是一段可执行的代码,类似Java的 Runnable
对于想做任务管理的开发者,只需要创建NSOperation 并且add 进入Queue即可顺序执行,值得注意的是NSInvokeOperation在Swift里被干掉了
Class | OC | Swift |
---|---|---|
NSBlockOperation | YES | YES |
NSInvocationOperation | YES | NO |
并发和异步(Concurrent and Asynchronous)
在 NSOperationQueue 里仅仅支持并发(Concurrent)并不支持串行(Parallel)
而在 NSOperation 中有一个只读属性 (Asynchronous)可以来控制该Operation是否异步开启
关于两者的区别在于
关键词 | 定义 |
---|---|
Concurrent | 决定了当前Queue作为线程池最多使用几个Thread |
Asynchronous | 标记当前Operation执行star()时是在当前Thread还是开启新的Thread |
苹果官方本身并不推荐用户操作 Asynchronous ,而且需要自己继承 NSOperation 之后控制KVO,并且在官方文档着重强调
1 | When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread. Therefore, if you always run operations by adding them to an operation queue, there is no reason to make them asynchronous. |
因为 NSOperationQueue 本身并没有Handler所有的KVO状态组合,所以在重写 Operation 时要着重注意
能否获取当前执行的Operation
如果不重写Operation无法获得,虽然我们拥有 queue.operations 数组
而且每当Operation进入了 isFinished 状态后就会从当前的队列移出并且被释放掉
但是因为有 queuePriority 和 maxConcurrentOperationCount的原因
self.queue.operations[0]并不一定是正在执行的Operation
简单控制重写Operation的注意点
继承了NSOperation之后第一种情况就是简单的重写main()函数
只需要控制好 isCancelled 标记位即可
其他的标记位不要重写KVO,即可完成 Synchronous同步执行某个Operation
复杂的Operation的main()中包含异步调用
通常我们重写NSOperation后还会包含很多异步接口,例如从网上下载图片
实现main函数中异步调用完成Operation有两种方法
使用信号量拥塞当前Thread
使用信号量控制十分简单,但是会拥塞当前Thread,Queue本来就是线程池
如果没有特殊需要推荐使用信号量
1 | let semaphore = DispatchSemaphore(value: 0) |
重写KVO来协助Queue完成控制
此时就需要更多的标记位来帮助我们控制Queue的执行,因为 NSOperationQueue 并没有Handler所有的KVO状态组合
所以一旦重写KVO之后的状态进入某种错误的组合,整个Queue队列会被卡死,
例如 isFinished = NO isCanceled = YES isReady = YES 的情况当前执行的任务既不会结束,也不会开始main()函数
下面列举一些个人操作中卡死的原因帮助排查
- 重写了Cancel方法,但是在Cancel后没有控制isFinish导致Queue无法完成,例如main函数中检测到Cancel直接就返回了
- isFinish没有和isExecuting状态一起更改,或者isFinish在isExecuting之前改变
- isReady的没有重写,导致任务无法开始卡死
- 逻辑结束后错误的把 isFinish 赋值为 False 或者把 isExecuting赋值为True
- 因为存在Concurrent,在上一个Operation还没有结束直接Cancel了有待执行的Operation,被Cancel的Operation根本没有执行就变成了Finish,卡死队列,正确的情况应该是 isCancel = YES , isFinished = NO
更详细的控制逻辑,可以参考GNU的开源NSOperation实现来减少卡死的行为