6-23.【架构设计】响应式系统中,为什么“取消(Cancel)”是架构能力的一部分?如果忽略取消,会发生什么?

3 阅读2分钟

一、本质:Cancel 的角色

在响应式系统中(Combine / Rx / TCA / Redux Observable):

每个 Effect / Publisher / Observable 都是时间流
它可能无限、异步、或者高频发生。
Cancel = 主动控制这些流的生命周期

核心理念:

  • 时间流不等于事件流完成
  • Cancel = “我不再关心这个流的输出,也不希望它污染 State/UI”

二、Cancel 在架构中的作用

1️⃣ 保持 UI 可预测性

高频事件或异步任务,如果不取消:

用户搜索:Task1
用户快速输入:Task2, Task3
  • 如果 Task1 没被取消 → Task1 结果仍会回到 State

  • UI 显示老数据 → 用户感知混乱

  • Cancel 可以保证:

    • 只保留最新相关任务的输出
    • old task 不污染 State

TCA / Rx 的 switchLatest 就是 Cancel 的具体实现。


2️⃣ 避免资源泄露(性能 / 内存)

  • 每个 Publisher / Effect 都是引用对象
  • 高频事件 + 长时间未完成 → 占用 ARC / 内存
  • Cancel = 主动释放资源,避免内存泄露和 CPU 占用

3️⃣ 保证状态一致性

  • Cancel 是架构能力,因为它决定了:
State = 真实可见的事实
  • 没有 Cancel → 过期事件修改 State → 双源真相 / UI 不一致
  • 有 Cancel → Reducer / State 只处理“当前有效”的事件

三、如果忽略 Cancel,会发生什么

问题类型描述示例
UI 不可预测异步旧事件更新 UI搜索 Task1 返回慢,覆盖 Task3 的结果
性能消耗高频流占用 CPU / 内存TextField 输入流未取消 → 每次输入生成 Publisher
状态混乱State 被过期事件污染网络缓存延迟返回旧数据 → Redux Store 错误显示
难调试异步流未控制,行为随机重复触发请求 → 随机顺序回调 → Bug 难复现

工程上,这种现象常被描述为:“响应式外壳 + 命令式内核”,因为外层看起来响应式,但内部流 uncontrolled。


四、架构实践

1️⃣ 使用 Cancelable / Cancellable

  • Combine: AnyCancellable
  • RxSwift: Disposable / DisposeBag
  • TCA: Effect.cancel(id:)
let task = api.fetch()
    .sink { ... }

cancellables.insert(task) // 可以 later cancel

2️⃣ 高频事件 → switchLatest / flatMapLatest

searchPublisher
    .map { query in api.search(query) }
    .switchToLatest() // 自动取消旧搜索
  • 用户只关心最新输入
  • 老搜索被取消 → 不污染 State / UI

3️⃣ 生命周期绑定

  • View 消失 → 所有绑定流 cancel
  • Task / Effect 在 ComponentScope / StoreScope 内绑定,自动释放

五、总结一句话

Cancel 是响应式架构能力的核心:它保证 UI 可预测、State 一致、资源可控。
忽略 Cancel → 响应式链条看似正确,实际内核充满“过期事件 + 竞态 + 内存泄露”,整个系统不可维护。