这三个操作符是 Combine 中处理多流聚合的核心工具。它们最本质的区别在于触发条件以及对数据配对的逻辑。
1. Merge(合并)
本质:将多个相同类型的流“揉”成一个。
merge 不会对数据做任何处理,它只是简单地把多个 Publisher 的输出汇集到同一个管道里。
-
触发机制:任何一个上游发出值,下游就立即收到该值。
-
类型要求:所有上游 Publisher 的
Output和Failure类型必须完全一致。 -
典型用法:
- 多来源更新:比如一个列表的数据可以来自“下拉刷新”、“点击按钮刷新”和“自动轮询”。这三个事件都可以
merge在一起,触发同一个网络请求。 - UI 事件汇总:将界面上多个按钮的点击流合并,执行统一的统计逻辑。
- 多来源更新:比如一个列表的数据可以来自“下拉刷新”、“点击按钮刷新”和“自动轮询”。这三个事件都可以
2. CombineLatest(最新组合)
本质:关注所有流的“当前状态”。
combineLatest 会保存每个上游发出的最后一个值。只要任何一个上游更新,它就会把所有流的最新值打包成一个元组(Tuple)发出来。
-
触发机制:
- 初始触发:必须等到所有上游都至少发过一次值后,下游才会开始工作。
- 后续触发:此后任何一个上游更新,下游都会收到一份“全家福”快照。
-
典型用法:
- 表单验证(最经典) :只有当“账号”、“密码”和“验证码”三个输入框都填了,且其中任何一个变化时,才重新计算“登录按钮”是否可用。
- 多维过滤:比如电商页面,同时根据“价格区间”、“分类”和“销量排序”三个筛选器的最新状态来请求搜索结果。
3. Zip(拉链配对)
本质:严格的一一对应。
zip 像拉链一样,必须左边一个齿和右边一个齿扣在一起。它会等待所有上游提供相同序号的值。
-
触发机制:只有当所有上游都发出了“第 个”值时,下游才会发出由这些第 个值组成的元组。
-
特性:它会“缓存”跑得快的流,等待跑得慢的流。如果一个流发了 10 个值,另一个只发了 1 个,下游只会收到 1 个组合值。
-
典型用法:
- 并行异步任务同步:同时发起两个互不相关的 API 请求(比如用户信息和用户资产),只有当两个都返回成功时,才把它们组合在一起显示。
- 数据比对:将“旧值流”和“新值流”通过偏移一位进行
zip,从而比较前后两个状态的差异。
4. 核心特性对比表
| 特性 | Merge | CombineLatest | Zip |
|---|---|---|---|
| 关注点 | 时间先后 | 状态快照 | 序列配对 |
| 输出形式 | 单个值( 或 ) | 元组(最新 + 最新 ) | 元组(第 个 + 第 个 ) |
| 触发前提 | 任一发出即触发 | 所有流都发过至少一次值 | 所有流都发出对应序号的值 |
| 类型限制 | 类型必须完全一致 | 类型可以不同 | 类型可以不同 |
| 结束时机 | 所有上游都结束 | 所有上游都结束 | 任一上游结束且无法配对 |
5. 防御式编程警示
-
CombineLatest 的“冷启动”延迟:
如果你的
combineLatest关联了 5 个流,只要其中 1 个一直没发值,整个下游就会处于“静默”状态。这在调试时经常被误认为代码没跑通。- 对策:给那些可能延迟发值的流提供一个
.prepend(defaultValue)初始值。
- 对策:给那些可能延迟发值的流提供一个
-
Zip 的内存隐患:
如果一个流产生的频率极高(如传感器数据),而另一个流极慢,
zip会在内存中缓存那个高频流产生的所有未配对数据。- 对策:避免将步调差异巨大的流进行
zip,或者给高频流加buffer或drop。
- 对策:避免将步调差异巨大的流进行
总结
- 谁来都行? 用
merge; - 我要所有人的最新状态? 用
combineLatest; - 必须成双成对? 用
zip。