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

无法在sink块中更新同一@Published变量,此现象是否符合预期?

问题解答

这是预期行为,和竞态条件无关——本质是Combine框架为了避免无限递归或嵌套触发,给@Published的发送逻辑加了保护限制,和你提到的willSet里设置值不会触发willSet是类似的设计思路。

背后的逻辑

当你在@Published变量的sink回调里直接修改该变量时,Combine会抑制这次修改触发的新订阅通知,甚至在当前回调的执行流程中,这个赋值的效果会被框架内部逻辑覆盖。这么做是为了防止出现无限循环(比如在sink里反复修改变量导致回调被无休止调用)。

你的代码具体分析

看你的示例代码:

  1. 调用presenter.state = .loading后,@Published的didSet触发,把.loading发送给sink回调。
  2. 进入sink后你尝试设置state = .finish,但此时正处于@Published处理上一次值变更的流程中,Combine会阻止这次赋值生效,也不会触发新的sink调用。
  3. 所以最后print(presenter.state)输出的还是.loading,完全符合这个机制的表现。

另外提一句:原代码里在sink里判断self.state == .ready是逻辑错误——因为sink触发时,state已经更新为newValue(也就是.loading)了,这个分支原本不应该被命中,可能是你测试时的笔误或者对@Published的触发时机理解有误。

解决方法

如果需要在状态变更后触发后续的状态更新,把赋值逻辑异步调度到当前事件循环之后执行即可,比如用Task包裹:

$state.sink { [weak self] newValue in
    guard let self else { return }
    switch newValue {
    case .loading:
        print("making this finish")
        // 异步调度,绕开当前sink的执行流程限制
        Task { @MainActor in
            self.state = .finish
        }
    default:
        break
    }
}
.store(in: &cancellables)

这样state = .finish会在当前sink回调执行完之后再执行,就能正常更新状态并触发后续的sink通知了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 05:26:19