SwiftUI中@Published修饰子类实例属性变更不触发视图更新如何解决
@Published 对引用类型(class)只会监听指针地址的变化,不会监听引用对象内部的属性变更:
你代码中定义的 People、SuperMankind、Mankind 全是 class 类型,ViewModel 中 @Published 修饰的 mankind 属性只有在被整体赋值新的 Mankind 对象时,才会触发视图更新通知。你修改的是 mankind.people 数组,mankind 本身的内存地址没有发生变化,因此 @Published 不会推送变更事件,视图就不会刷新。
你手动调用 viewModel.objectWillChange.send() 有效的原因,就是主动触发了 ObservableObject 的全局变更通知,告知 SwiftUI 需要刷新关联视图。
方案1:将数据模型改为值类型(优先选择)
如果没有必须使用 class 的业务需求(比如继承、共享引用状态),把所有数据模型改成 struct 即可自动解决问题:struct 是值类型,内部属性修改时整个值会发生变化,@Published 可以自动检测到变更。
修改后的参考代码:
// 无继承需求时改用struct,Identifiable可直接遵守 struct People: Identifiable { let id = UUID() var name: String init(name: String) { self.name = name } } struct Men { var people: [People] = [] } class ViewModel: ObservableObject { @Published var mankind = Men() }
如果需要保留泛型逻辑也可以对应调整为 struct 实现。
方案2:数据类遵守ObservableObject
如果必须保留 class 继承逻辑,让你的数据类也遵守 ObservableObject,内部可变属性加 @Published 修饰:
class SuperMankind<PlayerType: People>: ObservableObject { @Published var people: [PlayerType] = [] }
可以在 ViewModel 中手动订阅数据类的变更事件,转发到 ViewModel 自身的变更通知:
import Combine class ViewModel: ObservableObject { @Published var mankind: Mankind private var cancellable: AnyCancellable? init(_ m: Mankind) { mankind = m // 监听mankind的内部变更,转发到当前ViewModel的变更通知 cancellable = mankind.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() } } }
方案3:封装修改逻辑到ViewModel
不想改动现有数据层结构的话,把所有修改 mankind 内部属性的逻辑封装到 ViewModel 内部,修改完成后统一手动发送通知,避免在 View 层到处调用 send():
class ViewModel: ObservableObject { @Published var mankind: Mankind init(_ m: Mankind) { mankind = m } func addMen(name: String) { mankind.people.append(Men(name: name)) objectWillChange.send() } } // View层按钮点击直接调用封装好的方法 Button("Add") { viewModel.addMen(name: Int.random(in: 0...1000).description) }
内容的提问来源于stack exchange,提问作者Alex B

