8-4.【OC】【NSOperation】NSOperation 的取消机制是如何工作的?

3 阅读3分钟

NSOperation 的取消机制(Cancellation)并非“强制中断”,而是一种基于 状态标记“协作式(Cooperative)” 停止。它更像是一个礼貌的请求,而不是一个暴力的进程杀手。

理解它的工作原理,需要区分“状态切换”和“业务响应”两个层面。


1. 状态变更:isCancelled 信号

当你调用 operation.cancel()queue.cancelAllOperations() 时,内部会发生以下变化:

  • 状态翻转Operation 内部的私有布尔值 isCancelled 立即被置为 YES
  • KVO 通知:系统会发出 KVO 通知,告知该属性已更改。
  • 提前结束(仅限未开始的任务) :如果该任务还在 OperationQueue 的等待队列中(尚未开始执行),调度器在准备启动它时会检测到 isCancelledYES。此时,它会直接跳过 start 方法,将 isFinished 置为 YES

2. 协作式响应:开发者的责任

对于已经在运行中的任务,调用 cancel() 并不会自动停止你的代码(例如它不会中断一个正在跑的 for 循环或同步的网络请求)。

为了让取消真正生效,你必须在自定义的 mainstart 方法中频繁地手动检查 isCancelled 状态。

正确的实现范式:

Swift

class MyHeavyTask: Operation {
    override func main() {
        // 1. 开始前检查
        if isCancelled { return }
        
        for i in 0..<100 {
            // 2. 在每个耗时周期的循环中检查
            if isCancelled {
                // 执行必要的清理工作(如关闭文件句柄)
                return 
            }
            
            // 执行具体的重型逻辑...
            performComplexCalculation(at: i)
        }
    }
}

3. 依赖链中的取消行为

这是一个非常重要的细节:取消操作不会自动在依赖链中传播。

  • 独立性:如果 Task A 被取消了,它会立即进入 isFinished 状态。
  • 触发后续:由于 Task A 已经“完成”,依赖于它的 Task B 会认为依赖已解除,从而开始执行。
  • 应对方案:如果 Task B 必须在 Task A 成功执行后才能跑,你在 Task B 的开头需要检查 A 的取消状态或结果。

4. 为什么不设计成“暴力停止”?

苹果之所以设计成“协作式”,是出于数据一致性内存安全的考量:

  • 防止死锁:如果暴力停止,可能导致该线程持有的锁(Lock)永远无法释放。
  • 防止内存泄露:暴力中断可能导致已申请的堆内存或系统资源(如数据库连接、文件描述符)无法正常关闭。
  • 原子性:协作式允许你在退出前完成当前的原子操作,保证数据不被破坏。

5. 总结:取消机制的生命周期

阶段cancel() 调用后的表现开发者动作
Pending (队列中)自动跳过执行,直接标记为 Finished。无需额外处理。
Executing (运行中)仅将 isCancelled 设为 YES必须手动检查 isCancelled 并退出。
Finished (已完成)无任何影响。无。

💡 深度洞察:异步 Operation 的取消

如果你封装的是一个异步任务(如网络请求),调用 cancel() 时,你应该在重写的 cancel 方法中同步调用底层网络库(如 URLSessionTask)的 cancel,以确保网络连接被及时切断。