SwiftData中用ObservableObject替代@Query遇数据更新保存失效问题
问题:Core Data变更无法持久化,ObservableObject替代@Query后数据丢失
问题场景
因各视图使用@Query导致App严重卡顿,故改用根层级注入ObservableObject加载数据并全局复用。但执行Exercise的增删改操作后,即便显式调用保存,重启App后变更全部丢失;直接使用@Query则一切正常,怀疑是引用未更新导致。
自定义ObservableObject代码
class ExercisesManager: ObservableObject { let modelContext: ModelContext @Published var exercises: [Exercise] = [] var errorMessage: String? init(modelContext: ModelContext) { self.modelContext = modelContext fetch() } func fetch() { let sort = [SortDescriptor(\Exercise.name)] let fetchDescriptor = FetchDescriptor<Exercise>(sortBy: sort) do { exercises = try modelContext.fetch(fetchDescriptor) } catch { errorMessage = "DEBUG: Failed getting exercises: \(error.localizedDescription)" } } }
视图中使用方式
@EnvironmentObject private var manager: ExercisesManager @State private var exercises: [Exercise] = [] init() { .... } var body: some View { .... .onAppear { exercises = manager.exercises } }
问题分析
- 本地数组切断绑定:视图中用
@State复制manager.exercises到本地数组,后续manager的数据更新无法同步到视图,且操作本地数组不会影响modelContext中的数据。 - 未触发数据刷新:增删改操作后仅保存
modelContext,但未调用manager.fetch()更新@Published var exercises,导致manager持有的数组还是旧数据,视图也无法感知变化。 - 元素变更未触发Published更新:
@Published仅在数组被整体替换时发送通知,若仅修改数组内Exercise元素的属性,数组本身未变化,不会触发视图刷新。
解决方案
1. 移除视图本地数组,直接绑定manager数据
删除视图中的@State private var exercises,直接使用manager.exercises,确保视图始终与manager的数据绑定:
@EnvironmentObject private var manager: ExercisesManager var body: some View { List(manager.exercises) { exercise in Text(exercise.name) // 其他渲染内容 } }
2. 增删改操作后主动刷新数据
在ExercisesManager中封装增删改方法,操作完成后保存上下文并调用fetch()更新数组:
// 在ExercisesManager中添加方法 func addExercise(_ exercise: Exercise) { modelContext.insert(exercise) saveAndFetch() } func deleteExercise(_ exercise: Exercise) { modelContext.delete(exercise) saveAndFetch() } func saveAndFetch() { do { try modelContext.save() fetch() // 重新获取最新数据更新Published数组 } catch { errorMessage = "保存失败:\(error.localizedDescription)" } }
3. 自动监听上下文变化
给modelContext添加监听,当数据变更时自动刷新manager的数组,无需手动调用fetch():
class ExercisesManager: ObservableObject { let modelContext: ModelContext @Published var exercises: [Exercise] = [] var errorMessage: String? init(modelContext: ModelContext) { self.modelContext = modelContext fetch() // 监听上下文变更 NotificationCenter.default.addObserver( self, selector: #selector(refreshData), name: .NSManagedObjectContextObjectsDidChange, object: modelContext ) } func fetch() { let sort = [SortDescriptor(\Exercise.name)] let fetchDescriptor = FetchDescriptor<Exercise>(sortBy: sort) do { exercises = try modelContext.fetch(fetchDescriptor) } catch { errorMessage = "DEBUG: Failed getting exercises: \(error.localizedDescription)" } } @objc private func refreshData(_ notification: Notification) { // 可根据通知内容过滤不必要的刷新,避免频繁调用fetch fetch() } deinit { NotificationCenter.default.removeObserver(self) } }
关键注意事项
- 所有数据操作必须使用
manager持有的modelContext,确保上下文统一。 - 保存
modelContext时务必处理错误,确认保存操作成功。 - 避免在视图中复制
manager的数据源到本地状态,保持数据的单一来源。
内容的提问来源于stack exchange,提问作者TOPCOD3R
相关产品推荐
相关产品推荐

