SwiftUI init内赋值的@Published状态未刷新视图如何解决?
@Published属性未触发视图刷新的原理说明与修复
核心原理
@Published的刷新触发逻辑:@Published本质是对属性的willSet方法做了包装,只有当属性的内存地址发生变化(值类型赋值)或赋值的新对象与原有对象不是同一个引用(引用类型赋值)时,才会触发willSet回调,向ObservableObject的objectWillChange发布者推送事件,SwiftUI只有收到该事件才会刷新关联视图。- 引用类型集合的坑:你代码中用到的
List<Wallet>是Realm框架提供的集合类型,属于引用类型。当你从数据库查询到的wallets集合和当前userWallets指向同一个内存地址时,赋值操作不会触发willSet,自然不会推送刷新事件。你重启模拟器后生效是因为首次初始化时属性是全新赋值,引用地址确实发生了变化,所以能触发刷新。 - 回调线程问题:
Amplify.DataStore.query的异步回调默认运行在后台队列,你外层包裹的DispatchQueue.main.async仅把查询的发起动作放到了主线程,回调的执行依然在后台队列。如果@Published的事件在非主线程发送,SwiftUI会忽略该事件,甚至抛出运行时警告。
修复方案
代码修改示例
final class DataStorePersistanceHandler: ObservableObject { @Published var userWallets: List<Wallet> = List<Wallet>() let authSessionManager = AuthSessionManager() init() { getWallets() } // MARK: Get local/online user wallets func getWallets() { let user = authSessionManager.getUser() Amplify.DataStore.query(User.self, byId: user.userId) { [weak self] result in // 所有回调逻辑统一调度到主线程执行 DispatchQueue.main.async { guard let self = self else { return } switch result { case .success(let user): // 原代码此处变量名有误,查询的是User类型不是wallet if let userWithWallets = user, let wallets = userWithWallets.wallets { // 手动触发objectWillChange事件,即使引用不变也能推送刷新 self.objectWillChange.send() // 如果需要值语义避免后续集合变更不触发刷新,可转成Array或者copy一份List // self.userWallets = wallets.copy() as! List<Wallet> self.userWallets = wallets } else { print("No wallets found for this user") } case .failure(let error): print("User not found - \(error.localizedDescription)") } } } } }
额外优化建议
如果你的钱包数据会在数据库中实时更新,可以给userWallets添加Realm的集合变更通知,收到变更时手动调用objectWillChange.send()触发视图刷新,不需要每次都重新查询。
内容的提问来源于stack exchange,提问作者Arturo
相关产品推荐
相关产品推荐

