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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 05:06:04