You need to enable JavaScript to run this app. Combine 预取与缓存结合:如何实现无订阅不重启发布者直接返回缓存
优惠活动
大模型
产品
解决方案
定价
更多

Combine 预取与缓存结合:如何实现无订阅不重启发布者直接返回缓存

Combine 实现永久缓存的 shareReplay 操作符

问题根因

  • 你当前使用multicast + autoconnect的实现,问题出在autoconnect()的默认逻辑:它会跟踪订阅者数量,当最后一个订阅者取消订阅(数量降到0)时,自动取消对上游发布者的订阅;当新订阅者到来(数量升到1)时,会重新连接上游、触发全量数据拉取。
  • 即使你的自定义ShareReplySubject保留了历史缓存,上游重新连接后推送的新事件会覆盖原有缓存,不符合「无订阅时重启发布者直接返回旧缓存」的预期。

核心调整思路

要满足需求,需要修改连接管理逻辑:

  • 仅在第一个订阅者到来时触发一次上游连接,连接完成后永久保持上游订阅,不会因为订阅者数量降到0而断开
  • 长期持有带缓存的ShareReplySubject实例,保证无订阅时缓存不会被释放
  • 新订阅者到来时直接从缓存的Subject获取数据,不触发上游重新连接

具体实现代码

首先需要一个线程安全的共享状态管理类,持有Subject和上游连接句柄:

private final class ShareReplayState<Output, Failure: Error> {
    /// 带数据缓存的Subject,长期持有不被释放
    let subject: ShareReplySubject<Output, Failure>
    /// 上游连接的Cancellable,一旦建立就不会主动取消
    private var upstreamConnection: Cancellable?
    /// 保护状态的锁,避免多订阅者同时触发多次连接
    private let lock = NSLock()
    
    init(bufferSize: Int) {
        self.subject = ShareReplySubject(bufferSize: bufferSize)
    }
    
    /// 线程安全的获取/创建上游连接的方法
    func getOrCreateConnection(_ connectionBuilder: () -> Cancellable) -> Cancellable? {
        lock.lock()
        defer { lock.unlock() }
        if let existingConn = upstreamConnection {
            return existingConn
        }
        let conn = connectionBuilder()
        upstreamConnection = conn
        return conn
    }
}

然后修改shareReplay的实现,移除autoconnect(),手动管理连接生命周期:

extension Publisher {
    func shareReplay(_ bufferSize: Int = 1) -> AnyPublisher<Output, Failure> {
        /// 持有共享状态,只要返回的AnyPublisher不被释放,缓存和上游连接就会存活
        let state = ShareReplayState<Output, Failure>(bufferSize: bufferSize)
        
        /// 用Deferred包装订阅逻辑,只有当有订阅者时才执行内部判断
        return Deferred {
            // 线程安全的检查是否已经建立上游连接,未建立则发起一次永久连接
            state.getOrCreateConnection { [weak self] in
                guard let self = self else { return AnyCancellable {} }
                return self.multicast(subject: state.subject).connect()
            }
            // 直接返回带缓存的Subject给新订阅者
            return state.subject
        }
        .eraseToAnyPublisher()
    }
}

注意事项

  • 你需要把返回的AnyPublisher(即示例中的products)长期持有在内存中(比如存放在ViewModel、单例的属性中),否则对应的共享状态、缓存、上游连接都会被释放,无法实现永久缓存的效果。
  • 该实现只会触发一次上游数据拉取,后续所有订阅者都会直接复用缓存结果,不会重复发起网络请求、数据库查询等冷发布者任务。
  • 如果你兼容iOS 17及以上版本,可以直接使用原生提供的实现,无需自定义:
    products = getProducts.shareReplay(
        bufferSize: 1,
        scope: .forever
    )
    
    原生的.forever作用域完全符合你的需求,会自动管理上游连接、缓存,不会因订阅者数量变化重启上游。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 10:36:04