多ObservableObject中@AppStorage同步失效问题咨询
问题1:用法是否有误?是否属于不良实践?
你的用法本身没有明显错误,在多个ObservableObject中使用@AppStorage共享UserDefaults键值,从逻辑上来说是合理的——毕竟@AppStorage本质就是UserDefaults的包装器。这个同步问题是Xcode 14.3.1及15系列版本中的已知bug(你提交的FB13250915也确认了这一点),并非你的代码问题。
不过从架构最佳实践来说,分散在多个ObservableObject中管理同一个UserDefaults键确实会增加维护复杂度,更推荐用单一数据源统一管理所有持久化设置,避免多实例监听带来的同步风险。
问题2:其他开发者的解决方案
这个问题在开发者社区中已有不少反馈,常见的解决方案有以下几种:
方案1:改用单一全局ObservableObject管理设置
创建一个单例ObservableObject封装所有@AppStorage属性,所有视图和对象都依赖这个单例,确保只有一个@AppStorage实例监听键值变化:
final class AppSettings: ObservableObject { @AppStorage("test") var increment = 0 // 可添加更多@AppStorage属性 static let shared = AppSettings() } // 在View中使用: struct TabBView: View { @StateObject private var model = AppSettings.shared var body: some View { VStack { Text("\(model.increment)") Button { model.increment += 1 } label: { Text("Increment from ObservableObject") } } } }
这种方式从根源上避免了多实例同步问题,所有读写操作都通过同一个实例完成,@AppStorage的变化会正确触发视图刷新。
方案2:手动监听UserDefaults通知
如果必须保留多个ObservableObject实例,可以放弃@AppStorage,手动实现UserDefaults的读写和监听:
final class TabBModel: ObservableObject { // 从UserDefaults初始化值 var increment = UserDefaults.standard.integer(forKey: "test") { willSet { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: "test") } } private var notificationObserver: NSObjectProtocol? init() { // 监听UserDefaults全局变化通知 notificationObserver = NotificationCenter.default.addObserver( forName: UserDefaults.didChangeNotification, object: nil, queue: .main ) { [weak self] _ in guard let self = self else { return } let latestValue = UserDefaults.standard.integer(forKey: "test") if self.increment != latestValue { self.increment = latestValue } } } deinit { if let observer = notificationObserver { NotificationCenter.default.removeObserver(observer) } } }
通过手动监听UserDefaults.didChangeNotification,确保所有实例都能同步获取最新的UserDefaults数据。
方案3:使用第三方封装库
可以选择成熟的第三方库替代@AppStorage,比如Defaults,这类库通常已经处理了多实例同步问题,API设计也更简洁。
内容的提问来源于stack exchange,提问作者Clément Cardonnel

