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

如何从自定义Combine Subscriber中获取并存储Subscription订阅?

Swift Combine 自定义Subscriber获取Subscription的实现方案

核心原理:sink的内部实现逻辑

sink之所以能返回可持有的Cancellable(实际是Subscription的封装),是因为它内部的Subscriber是类类型的私有实现,会在收到Subscription的时候做中转存储,没有循环引用风险:

  • Combine内部的Sink subscriber是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 18:15:01