13-7.【Combine】Combine 中 .sink、.assign 的区别和适用场景是什么?

3 阅读2分钟

在 Combine 中,.sink.assign 是最常用的两个终端操作符(Subscribers) 。它们的主要区别在于赋值意图生命周期管理以及对错误的处理方式


1. .sink:多功能的“瑞士军刀”

.sink(接收)是最通用的订阅方式。它允许你通过闭包手动处理流中的每一个信号。

  • 核心特质完全控制。你可以处理数据,也可以处理完成/错误事件。

  • 适用场景

    • 执行非赋值类的操作(如打印日志、持久化存储、触发另一个网络请求)。
    • 需要处理错误逻辑(.failure)。
    • 需要对数据进行条件判断后再操作。

Swift

let cancellable = publisher.sink(
    receiveCompletion: { completion in
        switch completion {
        case .finished: print("任务完成")
        case .failure(let error): print("发生错误: (error)")
        }
    },
    receiveValue: { value in
        print("收到值: (value)")
    }
)

2. .assign:声明式的“传输带”

.assign(赋值)是一个专门为了属性绑定优化的快捷方式。它直接将 Publisher 发出的值设置到某个对象的属性上。

  • 核心特质简洁与强约束。它要求 Publisher 的错误类型必须是 Never(即该流永远不会报错)。

  • 两个变体

    1. .assign(to:on:) :传统的赋值方式。
    2. .assign(to: &$variable) :专门为 SwiftUI @Published 属性设计的安全方式。

.assign(to:on:) 的坑:

它会强引用 on 参数中的对象。如果这个对象(如 self)同时也持有了该订阅集合(cancellables),就会导致内存泄漏(Retain Cycle)

.assign(to: &$variable) 的优势:

这是 Swift 5.3+ 推出的“黑科技”。它不会产生循环引用,因为它会自动管理内部生命周期,且不返回 AnyCancellable,因为它直接将生命周期绑定到了目标 @Published 属性上。


3. 核心区别对比

特性.sink.assign
错误处理必须显式处理(除非 Failure 是 Never)必须先将 Failure 转为 Never 才能使用
赋值逻辑闭包内手动赋值自动完成赋值
内存风险需要配合 [weak self] 规避to:on: 极易导致内存泄漏
返回值返回 AnyCancellableto:on: 返回 AnyCancellableto:&$ 不返回
代码风格过程式、灵活声明式、简洁

4. 决策矩阵:我该选哪个?

  1. 如果你要把数据存进 @Published 属性

    • 优先选 .assign(to: &$data)。这是最安全、代码量最少的做法。
  2. 如果你要更新一个 UI 控件(如 UILabel.text)

    • 先用 .replaceError(with: "") 确保不会报错,然后用 .assign(to: .text, on: label)
  3. 如果你需要执行一段逻辑(如弹窗、跳转、打印)

    • 必须使用 .sink
  4. 如果你不确定这个流会不会报错

    • 使用 .sink 并在 receiveCompletion 中统一处理错误。

5. 防御式建议:关于 assign 的安全性

如果你不得不使用 .assign(to:on:) 且担心循环引用,又不想写冗长的 sink + [weak self],可以考虑在链条末尾先转换:

Swift

publisher
    .replaceError(with: "默认值")
    .sink { [weak self] in self?.label.text = $0 } // 比直接用 assign(to:on:) 更安全