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

自定义Combine Publisher包装类需手动保留才能运行,如何优化其生命周期管理?

问题根源

你遇到的问题本质是 Combine 的订阅链默认不会持有 Publisher 本身,你把状态(Thing 实例、代理实现)放在了作为类的 Publisher 里,作为局部变量的 Publisher 出了作用域就会被销毁,导致异步回调无法触发。
Combine 标准设计范式是:Publisher 是轻量的描述值生成规则的值类型(结构体),不持有运行时状态,所有状态都托管到被订阅链持有的 Subscription 实例中,只要你把 sink 返回的 Cancellable 存入持有集合,Subscription 就会一直存活直到订阅结束,不需要手动持有 Publisher。

解决方案

下面是符合 Combine 设计规范的修改后完整代码,无需手动持有 Publisher 即可正常运行:

import Foundation
import Combine

// 原有旧代码无需修改
protocol ThingDelegate: AnyObject {
    func delegateCall(number: Int)
}

class Thing {
    weak var delegate: ThingDelegate?
    var name: String = "Stuff"

    init() {
        Swift.print("thing init")
    }

    deinit {
        Swift.print("☠️☠️☠️☠️☠️☠️ thing deinit")
    }

    func start() {
        Swift.print("Thing.start()")

        DispatchQueue.main.async {
            self.delegate?.delegateCall(number: 99)
        }
    }
}

// 修改为结构体类型的Publisher
struct PublisherWrapper: Publisher {
    typealias Output = Int
    typealias Failure = Error

    private let thing: Thing
    init(thing: Thing) {
        self.thing = thing
        Swift.print("wrapper init")
    }

    func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Int == S.Input {
        // 所有状态都放到Subscription中,订阅链会自动持有Subscription实例
        let subscription = InnerSubscription(
            subscriber: subscriber,
            thing: thing
        )
        subscriber.receive(subscription: subscription)
    }
}

// 自定义Subscription,持有所有运行时状态
private class InnerSubscription<S: Subscriber>: Subscription, ThingDelegate where S.Input == Int, S.Failure == Error {
    private var subscriber: S?
    private let thing: Thing
    private let subject = PassthroughSubject<Int, Error>()
    private var cancellable: AnyCancellable?

    init(subscriber: S, thing: Thing) {
        self.subscriber = subscriber
        self.thing = thing
        self.thing.delegate = self
        // 绑定subject和订阅者
        self.cancellable = subject.subscribe(subscriber)
        self.thing.start()
    }

    func request(_ demand: Subscribers.Demand) {}

    func cancel() {
        // 订阅取消时清理资源
        cancellable = nil
        subscriber = nil
        thing.delegate = nil
    }

    // 实现代理协议
    func delegateCall(number: Int) {
        Swift.print("publisher delegate call: \(number)")
        self.subject.send(number)
        self.subject.send(completion: .finished)
        // 完成后自动清理
        cancel()
    }

    deinit {
        Swift.print("☠️☠️☠️☠️☠️☠️ subscription deinit")
    }
}

// 测试代码
class Test {
    var cancellables = Set<AnyCancellable>()
    // 无需再声明持有wrapper的属性

    func doSomething() {
        Swift.print("doSomething()")
        let thing = Thing()
        let wrapper = PublisherWrapper(thing: thing)
        // 不需要手动持有wrapper

        wrapper.sink { completion in
            print(completion)
            print("订阅结束")
        } receiveValue: {
            print("value: \($0)")
        }.store(in: &self.cancellables)
    }
}

print("start")
let t = Test()
t.doSomething()

效果说明

注释掉手动持有 Publisher 的代码后,依然可以正常收到回调值和完成事件,也能正常触发所有实例的析构方法,flatMap 等操作符可以直接使用,无需额外处理。

轻量简化方案

如果不想编写自定义 Subscription,也可以用 Deferred 闭包捕获状态的方式实现,代码更短:

func thingPublisher(thing: Thing) -> AnyPublisher<Int, Error> {
    Deferred {
        let subject = PassthroughSubject<Int, Error>()
        let proxy = DelegateProxy(subject: subject)
        thing.delegate = proxy
        thing.start()
        return subject
            .handleEvents(receiveCancel: {
                thing.delegate = nil
                _ = proxy // 强制持有proxy直到订阅结束
            })
    }
    .eraseToAnyPublisher()
}

private class DelegateProxy: ThingDelegate {
    let subject: PassthroughSubject<Int, Error>
    init(subject: PassthroughSubject<Int, Error>) {
        self.subject = subject
    }
    func delegateCall(number: Int) {
        subject.send(number)
        subject.send(completion: .finished)
    }
}

内容的提问来源于stack exchange,提问作者Yogurt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 01:06:09