13-8.【Combine】PassthroughSubject 与 CurrentValueSubject 的核心区别及应用场景?

0 阅读2分钟

在 Combine 中,Subject 是连接命令式代码(如按钮点击、代理回调)与响应式世界的桥梁。PassthroughSubjectCurrentValueSubject 的核心区别在于它们对“状态”的持有能力


1. 核心区别:状态与首个值

特性PassthroughSubjectCurrentValueSubject
是否持有状态。它只是一个“管道”。。它持有一个“当前值”。
初始化要求不需要初始值。必须提供初始值。
订阅瞬间保持沉默,直到下一次 send()立即收到当前持有的最新值。
同步访问无法直接获取当前值。可以随时通过 .value 同步读取/修改。

2. PassthroughSubject:即时的“事件”

本质:传递瞬时发生的信号。

它就像一个门口的门铃。按下去的时候响一声,如果你当时不在家(没订阅),你就永远错过了这一声铃响。它不记录过去发生的任何事情。

  • 应用场景

    • 用户交互:按钮点击、屏幕轻扫。
    • 通知/回调:简单的事件通知(如 userDidLogout)。
    • 单次触发:不需要知道历史状态,只关心“现在发生了什么”。

3. CurrentValueSubject:持久的“状态”

本质:代表一个随时间变化的值。

它就像一个室内的温度计。你任何时候看它,它都有一个当前数值。当你开始观察它时,它会立刻告诉你当前的温度,随后如果有变化,它会持续更新。

  • 应用场景

    • 数据流中心:ViewModel 中的 UI 状态(如 isLoggedIn, userName)。
    • 配置管理:当前的主题颜色、用户权限级别。
    • 同步检查:当你需要在代码中直接写 if subject.value == .loading 这种逻辑时。

4. 深度对比:选择哪一个?

场景 A:搜索框输入

如果你只想在用户打字时触发搜索:

  • 选 PassthroughSubject。因为之前的搜索词通常不再重要,你只关心“当前这一刻”输入了什么。

场景 B:设置页面的开关状态

  • 选 CurrentValueSubject。因为当你打开这个页面时,UI 必须知道开关当前是开还是关,从而进行初始化渲染。

5. 防御式设计建议:封装 Subject

无论使用哪种 Subject,为了保护模块边界,永远不要直接暴露 Subject 给外部

Swift

class UserViewModel {
    // 1. 内部私有:具有主动发送权
    private let stateSubject = CurrentValueSubject<ViewState, Never>(.idle)
    
    // 2. 外部只读:抹除 send() 能力,仅允许订阅
    var state: AnyPublisher<ViewState, Never> {
        stateSubject.eraseToAnyPublisher()
    }
}

总结

  • 如果你需要发送一个动作,用 PassthroughSubject
  • 如果你需要维护一个数据,用 CurrentValueSubject