NSOperation 的取消机制(Cancellation)并非“强制中断”,而是一种基于 状态标记 的 “协作式(Cooperative)” 停止。它更像是一个礼貌的请求,而不是一个暴力的进程杀手。
理解它的工作原理,需要区分“状态切换”和“业务响应”两个层面。
1. 状态变更:isCancelled 信号
当你调用 operation.cancel() 或 queue.cancelAllOperations() 时,内部会发生以下变化:
- 状态翻转:
Operation内部的私有布尔值isCancelled立即被置为YES。 - KVO 通知:系统会发出 KVO 通知,告知该属性已更改。
- 提前结束(仅限未开始的任务) :如果该任务还在
OperationQueue的等待队列中(尚未开始执行),调度器在准备启动它时会检测到isCancelled为YES。此时,它会直接跳过start方法,将isFinished置为YES。
2. 协作式响应:开发者的责任
对于已经在运行中的任务,调用 cancel() 并不会自动停止你的代码(例如它不会中断一个正在跑的 for 循环或同步的网络请求)。
为了让取消真正生效,你必须在自定义的 main 或 start 方法中频繁地手动检查 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,以确保网络连接被及时切断。