Core Data删除List后通过URI获取失效ObjectID问题求助
嘿,这个问题我太熟悉了——CoreData里的URI对应的是对象的永久标识符,哪怕你已经把List从上下文里删掉了,这个ID的URI表示还是能被解析成NSManagedObjectID,这就是为啥你调用viewContext.object(with:)会拿到一个故障状态的对象,而不是nil。
要修复selectedList() -> List?方法,你可以从两个方向入手:要么在获取对象时验证它是否真的存在,要么在删除List时提前清理无效的URI。下面是具体的实现方案:
方案一:使用existingObject(with:error:)验证对象存在性
object(with:)方法会直接返回一个故障对象(哪怕原数据已删除),而existingObject(with:error:)会尝试解析故障并验证对象是否存在于持久化存储中,如果不存在会抛出错误。这是CoreData官方推荐的验证对象存在性的方式:
func selectedList() -> List? { // 从UserDefaults取出保存的URI数据 guard let uriData = UserDefaults.standard.data(forKey: "selectedListURI"), let uri = URL(data: uriData), let objectID = persistentContainer.persistentStoreCoordinator.managedObjectID(forURIRepresentation: uri) else { return nil } do { // 尝试获取有效对象,不存在则抛出错误 let list = try viewContext.existingObject(with: objectID) as? List return list } catch { // 捕获到对象不存在的错误,清理UserDefaults里的无效URI UserDefaults.standard.removeObject(forKey: "selectedListURI") return nil } }
当对象已被删除时,existingObject会抛出CoreDataError.objectNotFound错误,这时候我们可以在catch块里清理UserDefaults的无效数据,避免下次启动还读取到这个URI。
方案二:手动刷新对象状态检查是否已删除
如果你不想用try-catch,也可以手动刷新对象状态,检查isDeleted属性:
func selectedList() -> List? { guard let uriData = UserDefaults.standard.data(forKey: "selectedListURI"), let uri = URL(data: uriData), let objectID = persistentContainer.persistentStoreCoordinator.managedObjectID(forURIRepresentation: uri) else { return nil } guard let list = viewContext.object(with: objectID) as? List else { return nil } // 强制从持久化存储刷新对象状态,确保isDeleted值准确 viewContext.refresh(list, mergeChanges: false) if list.isDeleted { UserDefaults.standard.removeObject(forKey: "selectedListURI") return nil } return list }
这里的refresh(_:mergeChanges:)会触发故障对象的加载,让isDeleted属性正确反映对象是否还存在于存储中。
额外的最佳实践:删除List时提前清理URI
为了从源头避免问题,建议你在删除List的逻辑里,检查如果删除的是当前选中的List,就直接清理UserDefaults里的URI:
func deleteList(_ list: List) { viewContext.delete(list) // 检查是否是当前选中的List if let selectedURI = UserDefaults.standard.data(forKey: "selectedListURI"), let uri = URL(data: selectedURI), let selectedID = persistentContainer.persistentStoreCoordinator.managedObjectID(forURIRepresentation: uri), selectedID == list.objectID { UserDefaults.standard.removeObject(forKey: "selectedListURI") } // 保存上下文 do { try viewContext.save() } catch { print("删除List失败:\(error.localizedDescription)") } }
这样下次启动时就不会读取到无效的URI,减少后续的判断逻辑。
内容的提问来源于stack exchange,提问作者Tysac

