13-20.【Combine】在 TCA / MVVM 架构中,Combine 流应该管理哪些副作用?

6 阅读3分钟

在 TCA (The Composable Architecture) 和 MVVM 架构中,Combine 流不仅是传递数据的管道,更是**管理“不确定性”**的核心。

副作用(Side Effects)是指那些改变系统状态、与外部世界交互或依赖时间的操作。在响应式架构中,Combine 流主要负责管理以下四类副作用:


1. 异步 I/O 任务(最核心的副作用)

这是 Combine 最擅长的领域。它将原本可能导致回调地狱的操作转化为线性的流。

  • 网络请求:通过 URLSession.DataTaskPublisher 管理。
  • 数据库读写:将 Core Data 或 Realm 的操作包装成 Publisher。
  • 文件系统操作:读取配置、写入缓存。

在架构中的表现:

  • MVVM:ViewModel 启动流,并在 sink 中更新 @Published 属性。
  • TCA:Reducer 返回一个 Effect(本质是封装好的 Publisher),由系统执行并将结果作为 Action 发回。

2. 外部事件源的“桥接”(桥接副作用)

许多系统级的副作用是命令式的。Combine 负责将这些持续发生的外部变化转化为受控的状态流。

  • 通知中心 (NotificationCenter) :监听键盘弹出、App 进入后台。
  • 传感器数据:CoreMotion、地理位置更新(CLLocationManager)。
  • 定时任务Timer.publish 产生的节拍。

3. 时间相关逻辑(时序副作用)

这在纯命令式代码中极难维护,但在 Combine 中只需一两个操作符。

  • 输入防抖 (Debounce) :搜索框输入。
  • 请求重试 (Retry) :网络抖动时的自动恢复。
  • 超时控制 (Timeout) :防止异步任务无限挂起。
  • 延迟执行 (Delay) :UI 动画的后续触发。

4. 状态同步与衍生(衍生副作用)

当一个状态的变化会引起一系列“连锁反应”时,Combine 负责管理这些衍生的副作用,确保同步的原子性

  • 表单验证:当 username 改变时,自动触发“检查用户名是否存在”的副作用。
  • 跨模块通知:当登录流成功后,自动触发“刷新购物车”和“拉取用户信息”两个并行的子流。

在 TCA vs MVVM 中的管理差异

维度MVVM 中的 CombineTCA 中的 Combine
存放位置散落在 ViewModel 的方法中统一在 Reducer 的 Effect 中返回
生命周期Set<AnyCancellable> 手动管理由 Store 自动关联 Action 生命周期
可测试性较难。需要 Mock 整个 ViewModel极强。因为 Effect 是声明式的,可以轻松模拟返回结果
控制力命令式触发流开始纯声明式。逻辑只负责“描述”副作用

⚠️ 防御式编程:哪些副作用不该用 Combine 管理?

  1. 纯视图导航:虽然可以用 Combine 触发页面跳转,但在 SwiftUI 中,使用底层的 NavigationStacksheet 绑定通常更直接,过度 Combine 化会导致逻辑碎片化。
  2. 高性能渲染逻辑:对于每秒 60/120 帧的绘图或复杂计算,Combine 的抽象层级(如 Generic Box 等)可能会引入不必要的开销,此时应回归到底层的命令式渲染循环。
  3. 单次同步计算:如果一个函数是纯粹的计算逻辑(Input -> Output),直接用函数即可,强行包装成 Just 流只会增加代码复杂度。

总结

在现代架构中,Combine 流应该作为副作用的“容器” 。它的任务是:捕获外部世界的混乱(异步、错误、频率),并将其过滤和提纯为 UI 可以直接消费的稳定状态。