删除多个核心数据对象:NSFetchedResultsController 的问题

Chr*_*nce 5 core-data nsfetchedresultscontroller ios

我已经找到了解决这个问题的方法,但我不太喜欢这个解决方法。我的问题是这样的。我正在使用 aNSFetchedResultsController来填充 UICollectionView——它显示图像集合。每个图像由核心数据对象描述(例如,其文件名在核心数据对象中)。

我有一个 UI 控件,允许用户同时删除多个图像,并且当用户删除多个对象时遇到问题。执行删除操作的代码是:

    for image in images {
       CoreData.sessionNamed(CoreDataExtras.sessionName).remove(image)
    }

    CoreData.sessionNamed(CoreDataExtras.sessionName).saveContext()
Run Code Online (Sandbox Code Playgroud)

(其中一些是我的库代码)。删除两个对象后,我收到崩溃和以下日志消息:

CoreData:错误:严重的应用程序错误。在核心数据更改处理期间捕获异常。这通常是 NSManagedObjectContextObjectsDidChangeNotification 观察者内的错误。无效更新:第 0 节中的项目数无效。更新后现有节中包含的项目数 (99) 必须等于更新前该节中包含的项目数 (101),加上或减去该数字从该部分插入或删除的项目数(0 插入,1 删除)以及加上或减去移入或移出该部分的项目数(0 移入,0 移出)。与用户信息(空)

如果我将删除代码更改为:

    for image in images {
        CoreData.sessionNamed(CoreDataExtras.sessionName).remove(image)
        CoreData.sessionNamed(CoreDataExtras.sessionName).saveContext()
    }
Run Code Online (Sandbox Code Playgroud)

我猜问题是在委托回调方法中:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
Run Code Online (Sandbox Code Playgroud)

我愿意:

collectionView.deleteItems(at: [indexPath]) 
Run Code Online (Sandbox Code Playgroud)

显然,您可以在方法中执行 reloadItems didChangeObject,也可以在每次删除对象后执行 saveContext。

pba*_*sdf 3

如果您删除多个图像,然后保存上下文,FRC 会处理所有删除内容 - 因此其sectionsfetchedObjects等会反映所有这些更改。但随后它会didChangeObject:为每个更改单独调用委托方法。在该方法中,您调用 collectionView 更新方法(例如deleteItems);然后,collectionView 调用其 dataSource 方法并进行快速统计:有 X 个项目,Y 个项目被删除,现在有 Z 个项目并抛出错误,因为 Z != XY。

当 FRC 与 tableView 一起使用时,可以通过使用 tableViewbeginUpdatesendUpdates调用 FRCcontrollerWillChangeContent:controllerDidChangeContent:委托方法来克服此问题。这会导致 tableView 推迟进行统计,直到处理完所有单独的更改 - 此时数字就会累加。

您的解决方案 - 在每次删除后调用saveContext- 导致 FRC 依次处理每个删除:更新其sectionsfetchedObjects等,以一次仅反映一个删除。这使 FRC 的数据与 collectionView 保持同步。一种可能的改进是processPendingChanges在每次删除后调用上下文,而不是保存上下文。这可以避免在您可能不想保存数据时保存数据,但仍然会导致每次删除都单独处理。

另一种方法是模仿 tableView 的 beginUpdates/endUpdates 机制来保存所有 collectionView 更新,直到处理完所有 FRC 更新。其工作原理大致如下:

  1. 创建数组来跟踪更改(插入、删除)。
  2. 每次didChangeObject:调用时,将对应的indexPath添加到相关数组中。
  3. 调用时controllerDidChangeContent:,迭代数组(首先删除,插入时),调用相应的 collectionView 更新方法。(然后清空数组,为下一批更新做好准备)。

这个问题及其答案中包含一些很好的解释和潜在的实现。