如何从自定义Combine Subscriber中获取并存储Subscription订阅?
Swift Combine 自定义Subscriber获取Subscription的实现方案
核心原理:sink的内部实现逻辑
sink之所以能返回可持有的Cancellable(实际是Subscription的封装),是因为它内部的Subscriber是类类型的私有实现,会在收到Subscription的时候做中转存储,没有循环引用风险:
- Combine内部的
Sinksubscriber是class,它会在receive(subscription:)回调中把收到的Subscription存到自己的属性里 - sink返回的
AnyCancellable实际持有了这个Sink实例,同时也持有了Subscription,当你在调用侧持有AnyCancellable的时候,就完成了Subscription的持有 - 当AnyCancellable释放时会自动调用
cancel(),同时释放Sink和Subscription,不会出现循环持有
自定义Subscriber获取Subscription的解决方案
你不需要在Subscriber内部强持有Subscription,用下面两种方式就能安全拿到:
方案1:用中间中转类存储Subscription
定义一个外部的持有容器,Subscriber只在收到Subscription的时候把它赋值给容器的属性,不需要自己持有:
// 先定义一个持有容器 class SubscriptionContainer { var subscription: Subscription? } // 自定义Subscriber class MySubscriber: Subscriber { typealias Input = String typealias Failure = Never // 弱持有容器,避免循环引用 weak var container: SubscriptionContainer? func receive(subscription: Subscription) { // 把Subscription存到外部容器里,不要存在自己的属性里强持有 container?.subscription = subscription subscription.request(.unlimited) } func receive(_ input: String) -> Subscribers.Demand { print("收到值:\(input)") return .none } func receive(completion: Subscribers.Completion<Never>) { print("收到结束:\(completion)") } } // 调用侧使用 let container = SubscriptionContainer() let subscriber = MySubscriber() subscriber.container = container let publisher = Just("test") publisher.subscribe(subscriber) // 现在你就可以通过container拿到subscription控制请求或者取消 container.subscription?.cancel()
方案2:直接让你的自定义Subscriber返回AnyCancellable
模仿sink的实现,你可以封装一个类似sink的方法,返回提前创建的AnyCancellable,在收到Subscription的时候把cancel逻辑绑定进去:
extension Publisher where Failure == Never { func customSink(receiveValue: @escaping (Output) -> Void) -> AnyCancellable { let subscriber = CustomSubscriber(receiveValue: receiveValue) // 提前创建AnyCancellable,取消时调用subscription的cancel let cancellable = AnyCancellable { subscriber.subscription?.cancel() } subscribe(subscriber) return cancellable } } private class CustomSubscriber<Input>: Subscriber { typealias Failure = Never var subscription: Subscription? let receiveValue: (Input) -> Void init(receiveValue: @escaping (Input) -> Void) { self.receiveValue = receiveValue } func receive(subscription: Subscription) { self.subscription = subscription subscription.request(.unlimited) } func receive(_ input: Input) -> Subscribers.Demand { receiveValue(input) return .none } func receive(completion: Subscribers.Completion<Never>) { subscription = nil } } // 调用侧直接持有返回的AnyCancellable即可,和sink用法完全一致 let cancellable = Just("test").customSink { value in print(value) }
常见疑问解答
- 为什么不会出现循环引用?:Subscription强持有Subscriber是订阅生命周期的要求,但是当你调用cancel或者订阅结束后,Subscription会自动释放对Subscriber的引用,只要你不在Subscriber里强持有Subscription,就不会出现循环。上面的两种方案里,要么是外部容器持有Subscription,要么是AnyCancellable持有取消逻辑,Subscriber对Subscription的存储只是临时的,订阅结束就会置空,不会有循环风险。
- Subscription不是类类型怎么弱引用?:确实
Subscription是协议,没有约束是类类型,所以你不能直接加weak修饰,但是你不需要弱引用它,只要保证持有链路是「调用侧 -> AnyCancellable/容器 -> Subscription -> Subscriber」,就不会出现内存问题,订阅结束后整个链路都会自动释放。
内容的提问来源于stack exchange,提问作者Plastech
相关产品推荐
相关产品推荐

