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

修改CoreData实体后视图未重绘,是否为Bug?

问题描述

我从网上借鉴了这个ViewModel的实现思路,它会在新上下文中创建或加载CoreData实体,然后保存结果。但编辑对象的弹窗关闭后,父视图并未更新以反映更改——尽管ViewModel中的对象标记为@Published,且我已经保存了上下文,这会不会是Bug?


代码片段

主视图代码

struct MainListView: View {
    
    // 引入CoreData单例实例
    var provider = ContactsProvider.instance
     
//    // sheet显示状态(编辑按钮已弃用该字段)
//    @State private var isShowing = false
    @State private var orgToEdit: OrgEntity?
    
    // 获取并绑定OrgEntity列表
    @FetchRequest(fetchRequest: OrgEntity.all()) private var orgEntitys
    
    var body: some View {
        NavigationStack {
            
            List {
                ForEach(orgEntitys) { org  in
                    NavigationLink(destination: OrgEntityDetailView(orgEntity: org)) {
                        MainListRow(orgEntity: org)
                            .swipeActions(allowsFullSwipe: true) {
                                // 删除/编辑按钮
                                Button(role: .destructive) {
                                    do {
                                        try provider.deleteOrg(org, context: provider.newContext)
                                    } catch {
                                        print("\(error)")
                                    }
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                                .tint(.red)
                                Button {
                                    orgToEdit = org
                                } label: {
                                    Label("Edit", systemImage: "pencil")
                                }
                                .tint(.orange)
                            }
                        
                    }
                }
            }
            .listStyle(.insetGrouped )
            .navigationTitle("My Map")
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button {
                        orgToEdit = .empty(context: provider.newContext)
                    } label: {
                        Image(systemName: "plus")
                            .font(.title2)
                    }
                }
                ToolbarItem(placement: .navigationBarLeading) {
                    Button {
                        
                    } label: {
                        Image(systemName: "map")
                    }

                }
            }
            .sheet(item: $orgToEdit, onDismiss: {
                orgToEdit = nil
            }, content: { org in    // 将闭包传入视图
                NavigationStack {
                    CreateOrgView(vm: .init(provider: provider, orgEntity: org ))
                }
            })
        
        }
    }
}

ViewModel代码

import Foundation
import CoreData

class EditOrgViewModel: ObservableObject {
    
    @Published var orgEntity: OrgEntity
    
    private let context: NSManagedObjectContext
    private let provider: ContactsProvider
    
    let isNew: Bool
    
    init(provider: ContactsProvider, orgEntity: OrgEntity? = nil) {
        // 设置为新的后台上下文
        self.provider = provider
        self.context = provider.newContext
        if let orgEntity,   
           let existingContactCopy =  provider.orgExists(orgEntity, in: context) {
            self.orgEntity = existingContactCopy
            self.isNew = false
        } else {
            self.orgEntity = OrgEntity(context: self.context)
            self.isNew = true
        }
            
    }
    
    func save() throws {
        try provider.persist(in: context)
    }
    
}

Provider核心代码(Exists()和Save()方法)

import Foundation
import CoreData
import SwiftUI

class ContactsProvider {
    
    // 创建单例
    static let instance: ContactsProvider = ContactsProvider()
    
    // 创建NSPersistentContainer容器
    let persistentContainer: NSPersistentContainer
    
    // 视图上下文计算属性
    var viewContext: NSManagedObjectContext {
        persistentContainer.viewContext
    }
    
    // 新上下文计算属性(用于编辑/添加实体)
    var newContext: NSManagedObjectContext {
        persistentContainer.newBackgroundContext()
    }
    
    private init() {
        persistentContainer = NSPersistentContainer(name: "ContactsDataModel")
        // 预览模式下使用内存上下文
        if EnvironmentValues.isPreview {
            persistentContainer.persistentStoreDescriptions.first?.url = .init(filePath: "/dev/null")
        }
        persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
        // 加载持久化存储
        persistentContainer.loadPersistentStores { _, error in
            if let error {
                fatalError("Unable to load store with error: \(error )")
            }
        }
    }
    
    func orgExists(_ orgEntity: OrgEntity, in context: NSManagedObjectContext) -> OrgEntity? {
        try? context.existingObject(with: orgEntity.objectID) as? OrgEntity
        // 根据ID在指定上下文中查找对应OrgEntity
    }
    
    func personExists(_ personEntity: PersonEntity, in context: NSManagedObjectContext) -> PersonEntity? {
        try? context.existingObject(with: personEntity.objectID) as? PersonEntity
    }
    
    func deleteOrg(_ orgEntity: OrgEntity, context: NSManagedObjectContext) throws {
        if let existingOrg = orgExists(orgEntity, in: context) {
            context.delete(existingOrg)
            // 异步保存CoreData变更
            Task(priority: .background) {
                try await context.perform {
                    try context.save()
                }
            }
        }
    }
    
    func deletePerson(_ personEntity: PersonEntity, context: NSManagedObjectContext) throws {
        if let existingPerson = personExists(personEntity, in: context) {
            context.delete(existingPerson)
            // 异步保存CoreData变更
            Task(priority: .background) {
                try await context.perform {
                    try context.save()
                }
            }
        }
    }
    
    func persist(in context: NSManagedObjectContext) throws {
        if context.hasChanges {
            try context.save()
        }
    }
    
}

// 判断是否处于预览模式
extension EnvironmentValues {
    static var isPreview: Bool {
        return ProcessInfo.processInfo.environment["XCODE_RUNNING _FOR_PREVIEWS"] == "1"
    }
}

问题分析与修复

这不是Bug,是CoreData多上下文同步的常见细节问题。父视图的@FetchRequest绑定的是viewContext,而编辑操作在background context中执行,虽然已经开启了automaticallyMergesChangesFromParent,但UI未刷新的原因及修复方案如下:

1. 手动触发FetchRequest刷新

@FetchRequest的结果不会总是立刻感知到跨上下文的变更,在sheet关闭时手动刷新即可:
修改主视图的sheet代码:

.sheet(item: $orgToEdit, onDismiss: {
    orgToEdit = nil
    orgEntitys.refresh() // 强制重新执行查询,获取最新数据
}, content: { org in
    NavigationStack {
        CreateOrgView(vm: .init(provider: provider, orgEntity: org ))
    }
})

2. 明确设置viewContext的合并策略

默认合并策略可能导致变更未正确合并,在ContactsProvider的初始化中添加:

private init() {
    persistentContainer = NSPersistentContainer(name: "ContactsDataModel")
    if EnvironmentValues.isPreview {
        persistentContainer.persistentStoreDescriptions.first?.url = .init(filePath: "/dev/null")
    }
    persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
    // 设置合并策略,确保其他上下文的变更能覆盖本地视图上下文的缓存
    persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    persistentContainer.loadPersistentStores { _, error in
        if let error {
            fatalError("Unable to load store with error: \(error )")
        }
    }
}

3. 修正删除操作的异步逻辑

原deleteOrg方法的Task嵌套会导致上下文保存时机不可控,改为直接执行并手动通知viewContext:

func deleteOrg(_ orgEntity: OrgEntity, context: NSManagedObjectContext) throws {
    if let existingOrg = orgExists(orgEntity, in: context) {
        context.delete(existingOrg)
        try context.save()
        // 通知视图上下文刷新所有对象
        persistentContainer.viewContext.perform {
            self.persistentContainer.viewContext.refreshAllObjects()
        }
    }
}

核心逻辑说明

CoreData通过持久化存储协调器同步多上下文的变更,automaticallyMergesChangesFromParent让viewContext自动接收变更,但UI层的@FetchRequest依赖NSManagedObject的KVO机制更新。手动调用刷新方法能确保UI及时获取最新数据。


内容的提问来源于stack exchange,提问作者srome11

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 08:49:57