You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Combine中Publisher转发至Subject时subscribe与sink的差异探究

关于Combine中Publisher转发至Subject的两种实现差异解析

在Combine中将Publisher事件转发至Subject时,存在两种常见实现方式,但二者的订阅持有逻辑存在明显差异:

  • publisher.sink { subject.send() }
  • publisher.subscribe(subject)

第一种实现:sink闭包转发

代码示例:

func pipeToSubject(from publisher: AnyPublisher<Void, Never>) -> AnyCancellable {
    let subject = PassthroughSubject<Void, Never>()
    return publisher.print().sink { subject.send() }
}

let publisher = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()
let cancellable = pipeToSubject(from: publisher)
cancellable.cancel()

对应输出:

receive subscription: (Empty)
request unlimited
receive cancel

逻辑解析:
sink操作符会创建一个一次性订阅者,闭包{ subject.send() }持有内部创建的subject。当调用cancellable.cancel()时,订阅者(sink)被释放,subject也随之失去引用被销毁,上游Publisher会收到取消信号,因此打印receive cancel,符合预期。

第二种实现:subscribe(subject)直接订阅

代码示例:

func pipeToSubject(from publisher: AnyPublisher<Void, Never>) -> AnyCancellable {
    let subject = PassthroughSubject<Void, Never>()
    return publisher.print().subscribe(subject)
}

let publisher = Empty<Void, Never>(completeImmediately: false).eraseToAnyPublisher()
let cancellable = pipeToSubject(from: publisher)
cancellable.cancel()

对应输出:

receive subscription: (Empty)

差异原因解析:
这不是Bug,而是Combine的设计逻辑导致的:

  1. PassthroughSubject本身遵循Subscriber协议,当调用publisher.subscribe(subject)时,上游Publisher会直接将订阅关系绑定到Subject实例上。
  2. 返回的AnyCancellable只是一个用于主动触发取消的令牌,但Subject内部会保留上游订阅的引用,同时上游Publisher也会持有Subject的引用(因为Subject是它的订阅者),形成临时引用循环。
  3. 当调用cancel()时,虽然触发了取消信号,但由于Subject还被上游订阅持有(未被释放),上游的订阅实例无法被销毁,因此不会打印receive cancel。

简单来说,subscribe(subject)的设计是让Subject成为上游Publisher的持久订阅者,订阅关系的生命周期与Subject绑定,而非仅由返回的Cancellable控制。只有当Subject本身被销毁时,上游订阅才会完全终止并触发receive cancel。

内容的提问来源于stack exchange,提问作者Ting Yi Shih

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 23:13:20