Swift Combine中如何让@Published属性的sink回调在didSet后执行
问题原因
这个现象是@Published的官方设计行为:它会在被包装属性的willSet生命周期中发射新值,所以sink回调必然发生在属性赋值完成、didSet触发之前,才会出现回调内访问实例属性拿到旧值的情况。
解决方案
方案1:使用receive(on:)调度回调执行
利用主队列的异步调度特性,将sink回调的执行延迟到当前主队列任务(也就是整个属性赋值流程)完成后再执行,仅需修改订阅代码即可,改造成本极低:
var subscriber = test.$array // 派发到主队列异步执行 .receive(on: DispatchQueue.main) .sink { values in print("arrayCount: \(test.array.count) valuesCount: \(values.count)") }
该方案适合大部分使用主队列更新UI的业务场景,不需要修改原有属性定义。
方案2:手动实现发布者,在didSet中发射值
如果需要更精准的时序控制,不依赖队列调度,可以弃用@Published,改用CurrentValueSubject/PassthroughSubject手动在didSet阶段发送新值:
import UIKit import Combine class Test { // 对外暴露只读发布者,外部只能订阅不能发送值 var arrayPublisher: AnyPublisher<[Int], Never> { _arraySubject.eraseToAnyPublisher() } // 内部私有的Subject,使用CurrentValueSubject保留初始值,和@Published行为对齐 private let _arraySubject = CurrentValueSubject<[Int], Never>([]) var array: [Int] = [] { willSet { print("willSet \(newValue.count)") } didSet { print("didSet \(array.count)") // didSet阶段再发送新值 _arraySubject.send(array) } } } var test = Test() // 订阅自定义的发布者 var subscriber = test.arrayPublisher.sink { values in print("arrayCount: \(test.array.count) valuesCount: \(values.count)") } print("1 arrayCount \(test.array.count)") test.array = [1, 2, 3] print("2 arrayCount \(test.array.count)") test.array = [1] print("3 arrayCount \(test.array.count)")
该方案的执行时序完全可控,不会受队列调度影响,适合对时序要求严格的场景。
内容的提问来源于stack exchange,提问作者Leszek Szary
相关产品推荐
相关产品推荐

