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

SwiftUI 嵌套ObservableObject属性修改后视图不更新问题求解

问题底层原理
  • @Published对引用类型的触发规则:@Published 只会在属性的「值」发生变更时发出通知。你的game属性是引用类型的Game类实例,存储的是实例的内存地址。当你调用viewModel.game.addPlayer修改的是Game内部的players属性,game本身的引用地址没有变化,所以@Published不会触发ContentViewModel的objectWillChange事件。
  • 初始化阶段的didSet不执行:Swift 类的初始化过程中,给自身存储属性赋值时不会触发didSet观察器。你在init(game: Game)里给self.game赋值时,didSet里的订阅逻辑完全没有运行,就算后续Game内部有变化,ContentViewModel也收不到任何通知,自然不会驱动视图刷新。
修复方案

方案1:补全订阅逻辑(最小改动适配现有代码)

只需要在ContentViewModel的初始化方法里手动触发一次订阅绑定,同时每次赋值新Game实例前取消旧订阅,避免内存泄漏即可。
修改后的ContentViewModel代码:

class ContentViewModel: ObservableObject {
    @Published var game: Game {
        didSet {
            bindGameChanges()
        }
    }
    var subscription: AnyCancellable?

    init(game: Game) {
        self.game = game
        // 初始化阶段手动触发绑定,弥补didSet不执行的问题
        bindGameChanges()
    }
    
    private func bindGameChanges() {
        // 先取消旧订阅,避免重复订阅和内存泄漏
        subscription?.cancel()
        subscription = game.objectWillChange.sink { [weak self] _ in
            self?.objectWillChange.send()
        }
    }
}

方案2:简化层级(无额外业务需求时更推荐)

如果ContentViewModel没有其他额外的业务逻辑需要封装,完全可以直接在View层监听Game实例,不需要多套一层ViewModel中转,代码更简洁:

struct ContentView: View {
    @ObservedObject var game: Game

    var body: some View {
        Text("Num players: \(game.players.count)")
            .padding()

        Button("Add player") {
            game.addPlayer("player")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 17:15:01