13-9.【Combine】map / flatMap / switchToLatest 的本质区别和应用场景是什么?

1 阅读3分钟

这三个操作符代表了 Combine 处理数据流时的三个层级:值的转换流的嵌套以及流的竞争。理解它们的关键在于观察它们如何处理“高阶 Publisher”(即产生 Publisher 的 Publisher)。


1. map:值的线性变换

本质:它是最基础的转换工具,作用于流中的每一个具体的值

  • 逻辑:进入一个值 AA,经过闭包计算,出来一个值 BB

  • 链条:它不会改变流的数量或结构,只是改变了流经管道的物质形态。

  • 场景

    • 将后端返回的 Data 转换为 Model
    • Int 转换为格式化后的 String 以供 UI 显示。

2. flatMap:流的摊平(降维)

本质:当你的转换逻辑返回的是另一个 Publisher 时,flatMap 会将这些子流的内容“摊平”到主流中。

  • 逻辑:进入一个值 AA,产生一个子流 P1P_1。随着时间推移,产生多个子流 P1,P2,P3P_1, P_2, P_3flatMap同时订阅所有子流,并将它们发出的值混合在一起发送。

  • 特性

    • 并行性:所有子流都是活跃的。
    • 乱序性:如果 P1P_1P2P_2 慢,那么 P2P_2 的值可能会先出现在主流中。
  • 场景

    • 批量上传文件:每选一个文件就启动一个上传流,所有文件的进度和结果都汇聚在同一个观察者那里。

3. switchToLatest:流的切换(抛弃旧爱)

本质:它专门用于处理“发布 Publisher 的 Publisher”。它保证同一时刻只有一个子流是活跃的

  • 逻辑:当主流发送了一个新的子流 PnewP_{new} 时,它会**立即取消(Cancel)**之前正在运行的旧子流 PoldP_{old},转而只监听最新的 PnewP_{new}

  • 特性排他性。它解决了“竞态条件(Race Condition)”问题。

  • 场景

    • 搜索框自动补全:用户输入 "A",发起请求 1;用户紧接着输入 "AB",发起请求 2。此时请求 1 已经没意义了。使用 switchToLatest 可以自动掐断请求 1,确保 UI 只显示最后一次输入的结果。

4. 核心区别对比表

特性mapflatMapswitchToLatest
转换目标\rightarrow\rightarrow流的流 \rightarrow 最新流
子流管理无子流全部保留,并行运行只留最新,取消旧的
内存开销极低较高(可能同时维持多个订阅)中等
典型用途数据格式化并行任务处理处理高频变动的异步任务(如搜索)

5. 防御式编程警示

  • flatMap 的内存陷阱

    默认情况下,flatMap 会永久持有所有子流。如果你的子流永不完成(比如是一个 Subject),内存会持续增长。建议通过 maxPublishers 参数限制并发数:.flatMap(maxPublishers: .max(1)) { ... }

    有趣的是:当 flatMapmaxPublishers 设为 .max(1) 时,它的行为有点像“排队”,但它不会switchToLatest 那样掐断旧任务,而是等旧任务完了再开新任务。

  • 错误处理的终结

    flatMap 内部的子流中,一定要使用 .catch 处理错误。如果子流抛出错误且未被捕获,整个主链条都会因为错误而永久终止,导致你的 UI 彻底失去响应。


总结

  • 简单变个形?用 map
  • 想让一堆任务一起跑?用 flatMap
  • 只要最新,旧的滚粗?用 switchToLatest