这三个操作符代表了 Combine 处理数据流时的三个层级:值的转换、流的嵌套以及流的竞争。理解它们的关键在于观察它们如何处理“高阶 Publisher”(即产生 Publisher 的 Publisher)。
1. map:值的线性变换
本质:它是最基础的转换工具,作用于流中的每一个具体的值。
-
逻辑:进入一个值 ,经过闭包计算,出来一个值 。
-
链条:它不会改变流的数量或结构,只是改变了流经管道的物质形态。
-
场景:
- 将后端返回的
Data转换为Model。 - 将
Int转换为格式化后的String以供 UI 显示。
- 将后端返回的
2. flatMap:流的摊平(降维)
本质:当你的转换逻辑返回的是另一个 Publisher 时,flatMap 会将这些子流的内容“摊平”到主流中。
-
逻辑:进入一个值 ,产生一个子流 。随着时间推移,产生多个子流 。
flatMap会同时订阅所有子流,并将它们发出的值混合在一起发送。 -
特性:
- 并行性:所有子流都是活跃的。
- 乱序性:如果 比 慢,那么 的值可能会先出现在主流中。
-
场景:
- 批量上传文件:每选一个文件就启动一个上传流,所有文件的进度和结果都汇聚在同一个观察者那里。
3. switchToLatest:流的切换(抛弃旧爱)
本质:它专门用于处理“发布 Publisher 的 Publisher”。它保证同一时刻只有一个子流是活跃的。
-
逻辑:当主流发送了一个新的子流 时,它会**立即取消(Cancel)**之前正在运行的旧子流 ,转而只监听最新的 。
-
特性:排他性。它解决了“竞态条件(Race Condition)”问题。
-
场景:
- 搜索框自动补全:用户输入 "A",发起请求 1;用户紧接着输入 "AB",发起请求 2。此时请求 1 已经没意义了。使用
switchToLatest可以自动掐断请求 1,确保 UI 只显示最后一次输入的结果。
- 搜索框自动补全:用户输入 "A",发起请求 1;用户紧接着输入 "AB",发起请求 2。此时请求 1 已经没意义了。使用
4. 核心区别对比表
| 特性 | map | flatMap | switchToLatest |
|---|---|---|---|
| 转换目标 | 值 值 | 值 流 | 流的流 最新流 |
| 子流管理 | 无子流 | 全部保留,并行运行 | 只留最新,取消旧的 |
| 内存开销 | 极低 | 较高(可能同时维持多个订阅) | 中等 |
| 典型用途 | 数据格式化 | 并行任务处理 | 处理高频变动的异步任务(如搜索) |
5. 防御式编程警示
-
flatMap的内存陷阱:默认情况下,
flatMap会永久持有所有子流。如果你的子流永不完成(比如是一个Subject),内存会持续增长。建议通过maxPublishers参数限制并发数:.flatMap(maxPublishers: .max(1)) { ... }。有趣的是:当
flatMap的maxPublishers设为.max(1)时,它的行为有点像“排队”,但它不会像switchToLatest那样掐断旧任务,而是等旧任务完了再开新任务。 -
错误处理的终结:
在
flatMap内部的子流中,一定要使用.catch处理错误。如果子流抛出错误且未被捕获,整个主链条都会因为错误而永久终止,导致你的 UI 彻底失去响应。
总结
- 简单变个形?用
map。 - 想让一堆任务一起跑?用
flatMap。 - 只要最新,旧的滚粗?用
switchToLatest。