SwiftUI下更新Core Data嵌套自定义对象属性的最优方案问询
问题原因拆解
- 前两种方案持久化失败的核心原因:Core Data的Transformable属性是值语义存储,你修改嵌套在里面的自定义对象属性时,Core Data检测不到托管对象本身的
listOfObject属性发生了变更,所以不会把更新后的序列化数据写入数据库。你手动发送的objectWillChange只是通知SwiftUI刷新UI,和Core Data的变更跟踪逻辑完全无关。 - 第三种方案崩溃的原因:
weight根本不是ObjectDetail实体的直属属性,是嵌套在Transformable类型listOfObject内部的字段。NSBatchUpdateRequest只能操作实体顶层的属性,完全无法识别序列化存储的自定义对象内部字段,自然会报属性不存在的错误。
最优解决方案
步骤1:修改属性时显式标记托管对象变更
改完嵌套对象的属性后,需要主动触发Core Data对listOfObject属性的变更跟踪,触发序列化逻辑,代码示例如下:
if let selectedObject = self.myObject.first(where: {$0.objectName == self.currentSelectedObjectName}) { guard let list = selectedObject.listOfObject, let targetElement = list.myListOfObject.first(where: {$0 == self.currentSelectedObject }) else { return } // 修改嵌套属性 targetElement.weight = 0 // 核心逻辑:主动标记托管对象的listOfObject属性发生变更 selectedObject.willChangeValue(forKey: "listOfObject") selectedObject.didChangeValue(forKey: "listOfObject") // 保存上下文 saveContext() }
步骤2:确认Transformable配置正确
检查.xcdatamodel文件中listOfObject属性的配置:
- 类型选择Transformable,Custom Class填写你定义的
ListOfObject完整类名(如果有模块前缀需要一同带上) - 确认
ListOfObject的NSCoding实现完整,encode(with:)和init?(coder:)方法已经正确序列化/反序列化了内部存储的所有对象的weight等属性,漏写序列化逻辑也会导致重启后数据恢复默认值。
步骤3:批量更新的替代方案
NSBatchUpdateRequest无法操作Transformable内部属性,批量更新直接遍历目标托管对象修改即可,常规数据量下性能完全够用:
mangedObjectContext.performAndWait { let fetchRequest: NSFetchRequest<ObjectDetail> = ObjectDetail.fetchRequest() // 按需添加NSPredicate过滤需要更新的对象 let results = try? mangedObjectContext.fetch(fetchRequest) results?.forEach { objectDetail in objectDetail.listOfObject?.myListOfObject.forEach { element in element.weight = 0 } // 标记属性变更触发序列化 objectDetail.willChangeValue(forKey: "listOfObject") objectDetail.didChangeValue(forKey: "listOfObject") } try? mangedObjectContext.save() }
可选优化:封装变更逻辑
把嵌套属性的修改逻辑封装到ObjectDetail的扩展中,避免重复编写标记代码:
extension ObjectDetail { func updateListElementWeight(_ element: ListElement, weight: Int) { guard let list = listOfObject, list.myListOfObject.contains(element) else { return } element.weight = weight willChangeValue(forKey: #keyPath(listOfObject)) didChangeValue(forKey: #keyPath(listOfObject)) } }
后续调用时直接执行selectedObject.updateListElementWeight(targetElement, weight: 0)即可,降低出错概率。
内容的提问来源于stack exchange,提问作者ConteOlaf
相关产品推荐
相关产品推荐

