修改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
相关产品推荐
相关产品推荐

