You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 }
}

问题分析

  1. 本地数组切断绑定:视图中用@State复制manager.exercises到本地数组,后续manager的数据更新无法同步到视图,且操作本地数组不会影响modelContext中的数据。
  2. 未触发数据刷新:增删改操作后仅保存modelContext,但未调用manager.fetch()更新@Published var exercises,导致manager持有的数组还是旧数据,视图也无法感知变化。
  3. 元素变更未触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:41:34