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

Swift跨类Combine Publisher/Subscriber无输出及选型疑问

问题描述

参考Combine基础示例修改的计数器代码可以正常打印1、2、3:

class MyCounter {
    var publisher: AnyPublisher<Int, Never> {
        subject.eraseToAnyPublisher()
    }

    private(set) var value = 0 {
        didSet { subject.send(value) }
    }

    private let subject = PassthroughSubject<Int, Never>()

    func increment() {
        value += 1
    }
}

class PrintCounter {
    
    let myCounter = MyCounter()

    init() {
        
        let cancellable = myCounter.publisher
            .sink { value in
                print(value)
            }

        myCounter.increment()
        myCounter.increment()
        myCounter.increment()
    }
}

let printCounter = PrintCounter()

但在实现Timer数据发布时,MyTimer类内的value已正常递增,PrintMyTimer的订阅者却无法接收数据:

class MyTimer {
    var subscriptions = Set<AnyCancellable>()

    var publisher: AnyPublisher<Int, Never> {
        subject.eraseToAnyPublisher()
    }

    private var value = 0 {
        didSet {
            subject.send(value)
        }
    }

    private let subject = PassthroughSubject<Int, Never>()
    
    init() {
        
        let currentDate = Date()
        
        Timer.publish(every: 1.0, on: .main, in: .common)
            .autoconnect()
            .map({ (output) in
                return output.timeIntervalSince(currentDate)
            })
            .map({ (timeInterval) in
                return Int(timeInterval)
            })
            .sink { (seconds) in
                print("MyTimer: \(seconds)")
                self.value = seconds
            }
            .store(in: &subscriptions)
    }
}

class PrintMyTimer {
    
    let myTimer = MyTimer()
    
    init() {
            
        let cancellable = myTimer.publisher
            .sink { value in
                print("PrintMyTimer: \(value)")
            }
    }
}

let printMyTimer = PrintMyTimer()

请教两个问题:

  1. 为何Timer示例中跨类的Subscriber无法接收到数据?
  2. 使用Combine实现这类定时、坐标数据的发布是否是正确的技术方向?

解答

1. 订阅失效的原因

问题出在PrintMyTimer的初始化方法里:你创建的cancellable是局部变量,当init方法执行完毕后,这个变量会被立即销毁,对应的订阅也就被取消了,自然接收不到后续的事件。

PassthroughSubject(以及所有Combine发布者)的订阅依赖于Cancellable实例的存活——只要Cancellable被释放,订阅就会终止。

修复方法很简单:在PrintMyTimer类中添加一个存储订阅的集合,把cancellable存进去:

class PrintMyTimer {
    let myTimer = MyTimer()
    // 添加集合存储订阅
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        myTimer.publisher
            .sink { value in
                print("PrintMyTimer: \(value)")
            }
            .store(in: &cancellables) // 将订阅存入集合
    }
}

这样cancellable会和PrintMyTimer实例一起存活,订阅就能持续接收MyTimer发送的事件了。

2. Combine是否适合这类场景?

完全适合,甚至是推荐的技术方向,原因如下:

  • 统一异步事件处理:定时器、位置更新、网络请求这类异步数据源,都可以用Combine的发布者模式统一处理,避免代理、闭包、NotificationCenter混合使用带来的代码混乱。
  • 简洁的事件转换与处理:通过map、filter、debounce等操作符可轻松对原始数据进行转换、过滤,逻辑清晰且链式调用可读性强。
  • 自动管理订阅生命周期:通过Set<AnyCancellable>集中管理所有订阅,避免内存泄漏或无效订阅。
  • UI绑定便捷:无论是UIKit(用assign绑定控件属性)还是SwiftUI(结合@Published),Combine都能轻松实现数据与UI的双向绑定,减少手动更新UI的代码。

对于位置坐标这类持续产生的异步数据,Combine同样能很好地封装CoreLocation的回调,将其转换为发布者,让订阅者专注于数据处理而非回调逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:05:41