Core Data保存后NSFetchedResultsController仍用临时ID致崩溃问题
问题分析
崩溃的核心原因是:新创建的Asset对象在Core Data上下文保存前使用的是临时NSManagedObjectID,调用context.save()后,Core Data会将临时ID替换为持久化的永久ID,但你手动构建的快照里存储的仍然是保存前的临时ID。当用户点击单元格时,用这个临时ID查找对象,此时临时ID对应的对象已被替换为永久ID的对象,自然找不到,抛出133000错误。
解决方案
方案1:直接使用FRC提供的快照(推荐)
FetchedResultsController在didChangeContentWith回调里提供的NSDiffableDataSourceSnapshotReference已经处理过永久ID,无需手动构建快照,直接转换后应用即可:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { guard let newSnapshot = snapshot as? NSDiffableDataSourceSnapshot<String, NSManagedObjectID> else { print("快照类型不匹配") return } dataSource.apply(newSnapshot, animatingDifferences: true) }
方案2:手动构建快照时确保获取永久ID
如果必须手动构建快照,需确保上下文保存完成后,从FRC结果中获取Asset的永久ID,同时修正FRC配置中的关系预取错误:
修正FRC获取函数
func performCoreDataFetch(){ guard let context = self.context else { return } let fetchRequest = Group.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)] // 修正预取关系名,对应Group里的一对多关系`assets` fetchRequest.relationshipKeyPathsForPrefetching = ["assets"] fetchRequest.predicate = NSPredicate(format: "ANY assets != nil") fetchResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: "name", cacheName: nil) fetchResultsController.delegate = self do { try fetchResultsController.performFetch() } catch { print("Fetch失败:\(error)") } }
修正快照构建逻辑
确保构建快照时,Asset的ID已为永久ID(上下文保存后FRC会自动更新结果):
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { var newSnapshot = NSDiffableDataSourceSnapshot<String, NSManagedObjectID>() guard let sections = controller.sections else { return } for sectionInfo in sections { guard let group = sectionInfo.objects?.first as? Group, let groupName = group.name else { continue } newSnapshot.appendSections([groupName]) // 从Group中获取已保存的Asset,过滤临时ID let permanentIDs = group.assets?.compactMap { asset in let id = asset.objectID return id.isTemporaryID ? nil : id } ?? [] newSnapshot.appendItems(permanentIDs, toSection: groupName) } dataSource.apply(newSnapshot, animatingDifferences: true) }
额外优化点
- Cell注册的错误处理:避免强制
try,增加容错逻辑:
let assetCell = UICollectionView.CellRegistration<CustomAssetCell, NSManagedObjectID> { (cell, indexPath, objectID) in guard let context = self.context else { return } do { if let asset = try context.existingObject(with: objectID) as? Asset { cell.imageView.image = asset.image } } catch { print("加载Cell数据失败:\(error)") cell.imageView.image = UIImage(systemName: "photo") // 设置默认占位图 } }
- 点击事件的安全校验:提前过滤临时ID,避免无效查找:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let objectID = self.dataSource.itemIdentifier(for: indexPath) else { collectionView.deselectItem(at: indexPath, animated: true) return } // 跳过临时ID,避免无效查找 guard !objectID.isTemporaryID else { print("无效的临时ObjectID") collectionView.deselectItem(at: indexPath, animated: true) return } guard let context = self.context else { collectionView.deselectItem(at: indexPath, animated: true) return } do { guard let asset = try context.existingObject(with: objectID) as? Asset else { collectionView.deselectItem(at: indexPath, animated: true) return } // 跳转详情页逻辑 if let assetDetail = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: MasterListViewController.assetDetailIdentifier) as? DetailViewController { assetDetail.context = self.context assetDetail.asset = asset let navController = UINavigationController(rootViewController: assetDetail) present(navController, animated: true) } } catch { print("获取对象失败:\(error)") collectionView.deselectItem(at: indexPath, animated: true) } }
内容的提问来源于stack exchange,提问作者robinyapockets
相关产品推荐
相关产品推荐

