在 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 中的 Combine | TCA 中的 Combine |
|---|---|---|
| 存放位置 | 散落在 ViewModel 的方法中 | 统一在 Reducer 的 Effect 中返回 |
| 生命周期 | 由 Set<AnyCancellable> 手动管理 | 由 Store 自动关联 Action 生命周期 |
| 可测试性 | 较难。需要 Mock 整个 ViewModel | 极强。因为 Effect 是声明式的,可以轻松模拟返回结果 |
| 控制力 | 命令式触发流开始 | 纯声明式。逻辑只负责“描述”副作用 |
⚠️ 防御式编程:哪些副作用不该用 Combine 管理?
- 纯视图导航:虽然可以用 Combine 触发页面跳转,但在 SwiftUI 中,使用底层的
NavigationStack或sheet绑定通常更直接,过度 Combine 化会导致逻辑碎片化。 - 高性能渲染逻辑:对于每秒 60/120 帧的绘图或复杂计算,Combine 的抽象层级(如 Generic Box 等)可能会引入不必要的开销,此时应回归到底层的命令式渲染循环。
- 单次同步计算:如果一个函数是纯粹的计算逻辑(Input -> Output),直接用函数即可,强行包装成
Just流只会增加代码复杂度。
总结
在现代架构中,Combine 流应该作为副作用的“容器” 。它的任务是:捕获外部世界的混乱(异步、错误、频率),并将其过滤和提纯为 UI 可以直接消费的稳定状态。