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
相关产品推荐
相关产品推荐

