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()
请教两个问题:
- 为何Timer示例中跨类的Subscriber无法接收到数据?
- 使用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
相关产品推荐
相关产品推荐

