一、本质区别
假设有一个 Publisher(Combine / Rx):
sourcePublisher: A
你想根据它生成 B 类型的数据流,可以用 map / flatMap / switchLatest,但它们的处理方式本质不同。
1️⃣ map
sourcePublisher.map { a in f(a) }
-
本质:一对一转换
-
类型:
A -> B -
行为:
- 每个输入事件
a→ 输出f(a) - 输出流长度 = 输入流长度
- 每个输入事件
-
异步性:
- 输出值同步产生(除非 f 返回 Publisher,再用 flatMap 包裹)
-
特点:简单、轻量、不会改变事件流数量
💡 工程直觉:用来做单次同步转换,比如 Int -> String 或 Model -> ViewModel。
2️⃣ flatMap
sourcePublisher.flatMap { a in asyncPublisher(a) }
-
本质:把一个事件映射成一个新的 Publisher,并把这些内部 Publisher 的事件“合并”到同一个输出流
-
类型:
A -> Publisher<B>→ 输出 B -
行为:
- 所有内部 Publisher 并行订阅
- 输出事件顺序可能交错
-
异步性:
- 支持异步任务、网络请求等
-
特点:
- 每个输入事件触发一个新的 Publisher
- 输出流混合所有内部 Publisher 的事件
-
🔥 风险:高频输入 → 多个并行网络请求 → 可能资源浪费 / 并发混乱
💡 工程直觉:输入事件触发独立异步任务,需要并行处理并保留所有结果。
3️⃣ switchLatest (Combine) / flatMapLatest (Rx)
sourcePublisher
.map { a in asyncPublisher(a) }
.switchToLatest()
-
本质:内部 Publisher 可切换,只保留最新的内部 Publisher 的输出
-
行为:
- 新输入事件 → 取消上一个内部 Publisher
- 输出只来自最新 Publisher
-
异步性:
- 适合“只关心最新任务结果”场景
-
特点:
- 避免旧任务干扰 UI
- 控制并发资源占用
💡 工程直觉:输入是高频事件(如搜索、滑动、输入) → 只关心最新结果。
二、行为总结表
| Operator | 输入 → 输出 | 并发 | 保留旧事件 | 典型场景 |
|---|---|---|---|---|
| map | 1:1 转换 | 无 | N/A | 数据映射 / Model → ViewModel |
| flatMap | 1 → Publisher → 输出合并 | 并行 | 是 | 并行网络请求 / 多任务汇总 |
| switchLatest | 1 → Publisher → 输出最新 | 串行(旧取消) | 否(取消旧任务) | 高频输入 → 只保留最新结果 |
三、架构层面的选择原则
1️⃣ map → 同步 / 派生状态
- 用于 State → DerivedState 或 Model → ViewModel
- 不改变事件流结构
- 性能轻,预测性好
2️⃣ flatMap → 并行异步操作
-
用于 State 触发多任务
-
保留所有结果 → Reducer 或 Adapter 处理
-
注意:
- 并发数量控制(避免 flood / race)
- 输出混合 → Reducer 必须能处理乱序数据
3️⃣ switchLatest → 高频事件 + 最新任务
- 用于 UI 高频事件(搜索框、滑动、节流动画)
- 自动取消旧任务 → 减少资源占用
- 可与 State / Reducer 结合 → 保持 UI 一致性
四、架构落地直觉
输入事件 → 映射转换 → 异步任务 → Reducer / Store
- map → State 派生 / ViewModel 映射
- flatMap → 并行 Effect / 网络 / DB
- switchLatest → 高频输入 → 异步请求 → 保持最新 UI
核心原则:选择 Operator = 根据事件频率 + 是否保留历史 + 是否需要并行处理