8-3.【OC】【NSOperation】NSOperation 的依赖关系是如何实现的?

0 阅读3分钟

NSOperation 的依赖关系(Dependencies)是其相对于 GCD 最具竞争力的特性。它允许我们将复杂的业务逻辑拆解为多个独立的原子操作,并像“搭积木”一样将它们连接起来。

其内部实现主要依赖于 KVO(Key-Value Observing)机制状态机控制以及一个内部依赖表


1. 核心模型:有向无环图 (DAG)

在数据结构层面,当你为多个 Operation 添加依赖时,你在底层实际上构建了一个有向无环图 (Directed Acyclic Graph)

  • 节点 (Nodes) :每一个 NSOperation 实例。
  • 边 (Edges)addDependency: 方法建立的指向关系。
  • 规则:调度器(OperationQueue)会遍历这张图,只有当一个节点的所有“入度”(即前置依赖)都消失时,该节点才会被激活。

2. 状态驱动:isReady 属性

NSOperation 内部有一个名为 isReady 的布尔属性。这是决定任务能否被 OperationQueue 调度执行的唯一关卡。

依赖检查的逻辑:

  1. 添加依赖:当你执行 opB.addDependency(opA) 时,opB 会将 opA 加入其内部的一个私有集合中。
  2. KVO 监听opB 会通过 KVO 监听 opAisFinished 属性。
  3. 状态变更:当 opA 完成任务(无论是因为执行完毕还是被取消),它的 isFinished 变为 YES
  4. 通知响应opB 收到 KVO 通知后,会检查自己所有依赖的任务。如果全部都处于 isFinished 状态,opB 就会通过 KVO 发出通知,告知外界自己的 isReady 变为 YES

3. 跨队列依赖 (Cross-Queue Dependencies)

这是 NSOperation 最强大的地方之一:依赖关系可以跨越不同的 OperationQueue

  • 原理:因为依赖机制是基于对象本身的状态(isFinished)和 KVO,而不是由特定的队列管理的。
  • 场景:你可以在 后台队列 中放一个“解压文件”的任务,在 主线程队列 中放一个“弹出提示框”的任务。只需让 UI 任务依赖于后台任务,UI 任务就会在后台任务完成的一瞬间自动被主线程调度。

4. 源码级的关键行为

  • 避免死锁NSOperation 内部会检测循环依赖(例如 A 依赖 B,B 依赖 A)。虽然系统不一定会抛出崩溃,但会导致这两个任务永远处于等待状态。
  • 内存释放:一旦一个任务完成,其依赖的任务列表通常会被清理,以释放对前置任务对象的强引用。
  • 取消传播注意,取消操作不会自动沿着依赖链传播。如果 opA 被取消了,它会立即进入 isFinished 状态,从而触发 opB 开始执行。如果 opB 也需要停止,你必须手动处理。

5. 依赖关系 vs GCD 组

特性NSOperation 依赖GCD Dispatch Group
逻辑结构1 对 1 或 1 对多,精确链接多对 1,侧重“汇总”
动态性运行期间可动态添加依赖必须在启动前加入组
灵活性支持跨队列、跨线程依赖仅限加入同一个 Group 的任务

💡 深度洞察:如何构建安全的依赖链?

在实际开发中,如果 opB 依赖 opA 是为了获取 opA 处理后的数据,你需要确保在 opB 开始执行的第一行代码中,通过 self.dependencies 找到 opA 并安全地取出结果。