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的设计逻辑导致的:
PassthroughSubject本身遵循Subscriber协议,当调用publisher.subscribe(subject)时,上游Publisher会直接将订阅关系绑定到Subject实例上。- 返回的
AnyCancellable只是一个用于主动触发取消的令牌,但Subject内部会保留上游订阅的引用,同时上游Publisher也会持有Subject的引用(因为Subject是它的订阅者),形成临时引用循环。 - 当调用
cancel()时,虽然触发了取消信号,但由于Subject还被上游订阅持有(未被释放),上游的订阅实例无法被销毁,因此不会打印receive cancel。
简单来说,subscribe(subject)的设计是让Subject成为上游Publisher的持久订阅者,订阅关系的生命周期与Subject绑定,而非仅由返回的Cancellable控制。只有当Subject本身被销毁时,上游订阅才会完全终止并触发receive cancel。
内容的提问来源于stack exchange,提问作者Ting Yi Shih
相关产品推荐
相关产品推荐

