【问题标题】:Crash while replacing NSFetchedResultsController for UICollectionView during CoreData updates在 CoreData 更新期间为 UICollectionView 替换 NSFetchedResultsController 时崩溃
【发布时间】:2016-11-20 19:56:04
【问题描述】:

我有一个 UICollectionView,它从 NSFetchedResultsController 接收它的内容。 数据来自多个 API-Endpoints(服务器的设计不在我手中)。 第一次调用提供元数据,然后对于每个条目 (> 10.000),必须有一个 API-Call 来获取相关数据。

CollectionView 可以针对多个属性进行过滤,每个过滤器需要不同的 sectionNameKeyPath

因此,当用户更改过滤器时,我将不得不用新的 FRC 替换当前的 FRC。但是当 CoreData 更新时,应用程序会崩溃

*** Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UICollectionView.m:4422
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 9 from section 0 which only contains 1 items before the update'

那么有什么办法可以避免这种崩溃吗?

编辑:为 NSFetchedResultsControllerDelegate 添加代码:

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
   _objectChanges = [NSMutableDictionary dictionary];
   _sectionChanges = [NSMutableDictionary dictionary];
}

- (void)controller:(NSFetchedResultsController *)controller
 didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
          atIndex:(NSUInteger)sectionIndex
    forChangeType:(NSFetchedResultsChangeType)type
{


   if (type == NSFetchedResultsChangeInsert || type == NSFetchedResultsChangeDelete) {
       NSMutableIndexSet *changeSet = _sectionChanges[@(type)];
       if (changeSet != nil) {
           [changeSet addIndex:sectionIndex];
       } else {
           _sectionChanges[@(type)] = [[NSMutableIndexSet alloc] initWithIndex:sectionIndex];
       }
   }

}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
      atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
     newIndexPath:(NSIndexPath *)newIndexPath
{

   NSMutableArray *changeSet = _objectChanges[@(type)];
   if (changeSet == nil) {
       changeSet = [[NSMutableArray alloc] init];
       _objectChanges[@(type)] = changeSet;
   }

   switch(type) {
       case NSFetchedResultsChangeInsert:
           [changeSet addObject:newIndexPath];
           break;
       case NSFetchedResultsChangeDelete:
           [changeSet addObject:indexPath];
           break;
       case NSFetchedResultsChangeUpdate:
           [changeSet addObject:indexPath];
           break;
       case NSFetchedResultsChangeMove:
           [changeSet addObject:@[indexPath, newIndexPath]];
           break;
   }

}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{

   NSMutableArray *moves = _objectChanges[@(NSFetchedResultsChangeMove)];
   if ([moves count] > 0) {
       NSMutableArray *updatedMoves = [[NSMutableArray alloc] initWithCapacity:[moves count]];

       NSMutableIndexSet *insertSections = _sectionChanges[@(NSFetchedResultsChangeInsert)];
       NSMutableIndexSet *deleteSections = _sectionChanges[@(NSFetchedResultsChangeDelete)];
       for (NSArray *move in moves) {
           NSIndexPath *oldIndexPath = move[0];
           NSIndexPath *newIndexPath = move[1];

           if ([deleteSections containsIndex:[oldIndexPath section]]) {
               if (![insertSections containsIndex:[newIndexPath section]]) {
                   NSMutableArray *changeSet = _objectChanges[@(NSFetchedResultsChangeInsert)];
                   if (changeSet == nil) {
                       changeSet = [[NSMutableArray alloc] initWithObjects:newIndexPath, nil];
                       _objectChanges[@(NSFetchedResultsChangeInsert)] = changeSet;
                   } else {
                       [changeSet addObject:newIndexPath];
                   }
               }
           } else if ([insertSections containsIndex:[newIndexPath section]]) {
               NSMutableArray *changeSet = _objectChanges[@(NSFetchedResultsChangeDelete)];
               if (changeSet == nil) {
                   changeSet = [[NSMutableArray alloc] initWithObjects:oldIndexPath, nil];
                   _objectChanges[@(NSFetchedResultsChangeDelete)] = changeSet;
               } else {
                   [changeSet addObject:oldIndexPath];
               }
           } else {
               [updatedMoves addObject:move];
           }
       }

       if ([updatedMoves count] > 0) {
           _objectChanges[@(NSFetchedResultsChangeMove)] = updatedMoves;
       } else {
           [_objectChanges removeObjectForKey:@(NSFetchedResultsChangeMove)];
       }
   }

   NSMutableArray *deletes = _objectChanges[@(NSFetchedResultsChangeDelete)];
   if ([deletes count] > 0) {
       NSMutableIndexSet *deletedSections = _sectionChanges[@(NSFetchedResultsChangeDelete)];
       [deletes filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSIndexPath *evaluatedObject, NSDictionary *bindings) {
           return ![deletedSections containsIndex:evaluatedObject.section];
       }]];
   }

   NSMutableArray *inserts = _objectChanges[@(NSFetchedResultsChangeInsert)];
   if ([inserts count] > 0) {
       NSMutableIndexSet *insertedSections = _sectionChanges[@(NSFetchedResultsChangeInsert)];
       [inserts filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSIndexPath *evaluatedObject, NSDictionary *bindings) {
           return ![insertedSections containsIndex:evaluatedObject.section];
       }]];
   }

   UICollectionView *collectionView = self.collectionView;
   [collectionView performBatchUpdates:^{

       NSIndexSet *deletedSections = _sectionChanges[@(NSFetchedResultsChangeDelete)];
       if ([deletedSections count] > 0) {
           [collectionView deleteSections:deletedSections];
       }

       NSIndexSet *insertedSections = _sectionChanges[@(NSFetchedResultsChangeInsert)];
       if ([insertedSections count] > 0) {
           [collectionView insertSections:insertedSections];
       }

       NSArray *deletedItems = _objectChanges[@(NSFetchedResultsChangeDelete)];
       if ([deletedItems count] > 0) {
           [collectionView deleteItemsAtIndexPaths:deletedItems];
       }

       NSArray *insertedItems = _objectChanges[@(NSFetchedResultsChangeInsert)];
       if ([insertedItems count] > 0) {
           [collectionView insertItemsAtIndexPaths:insertedItems];
       }

       NSArray *reloadItems = _objectChanges[@(NSFetchedResultsChangeUpdate)];
       if ([reloadItems count] > 0) {
           [collectionView reloadItemsAtIndexPaths:reloadItems];
       }

       NSArray *moveItems = _objectChanges[@(NSFetchedResultsChangeMove)];
       for (NSArray *paths in moveItems) {
           [collectionView moveItemAtIndexPath:paths[0] toIndexPath:paths[1]];
       }
   } completion:^(BOOL finished) {
       [[self refreshControl] endRefreshing];

   }];

   _objectChanges = nil;
   _sectionChanges = nil;

}

【问题讨论】:

  • 你是如何处理委托回调的?只是重新加载或尝试为单个更改设置动画?
  • 我都试过了,我使用 performBatchUpdates:在 controllerDidChangeContent 和块中,我为 itemChanges 和 sectionChanges 处理两个 mutableDictionaries。但为了测试,我还在 controllerDidChangeContent 中尝试了简单的“reloadData”。
  • 显示更改处理的代码以及如何将更改保存到核心数据中
  • 我在上面添加了 FRC-DelegateCode。插入新数据:我所做的是: - NSJSONSerialization 将 JSON 数据转换为字典 - 然后我创建一个临时 NSMangedObjectContext(使用 MainContexts PersistantStore 的 NSPrivateQueueConcurrencyType) - 在 localContext 上我查询需要更改的 MO ...如果发现我改变的是。如果所有数据都已处理,我会保存 localContext 并触发与 MainContext 的合并
  • 这里的关键部分是每组更改保存一次,并且在发生更改和进行任何更改之间存在延迟,因此更改有时间进入集合视图跨度>

标签: ios objective-c core-data uicollectionview nsfetchedresultscontroller


【解决方案1】:

虽然您的批处理代码看起来既聪明又高效,但很难确保这批更改是有说服力的。如果插入或删除任何部分,或者移动任何项目,那么很难确保所有其他索引路径都正确并引用适当的项目。

如果有任何部分插入或删除,我会重新加载。如果同时有移动的项目和其他任何东西,我会重新加载。如果您在同一部分同时有多个插入和删除,它可以工作,它可能会用错误的数据刷新屏幕。

您需要非常小心地对集合中的部分和部分内容进行复合更改。

您没有为您的 FRC 更改显示任何代码,我想这无关紧要。切换时一定要重新加载。

【讨论】:

  • 哦,我想我误解了您的“更改处理代码” 对于 FRC 更改,我尝试了几件事。在任何 FRC 更改发生之前的那一刻,我删除了委托并将当前 FRC 设置为 nil,甚至重新加载 CollectionView。然后只需创建一个新的 FRC
  • 您有用于将更改处理保存到核心数据中的代码,并且在 FRC 委托方法中,我同时要求两者,一切都很好。 FRC 更改是指在不同 FRC 实例之间切换,您的描述听起来不错
  • 我的问题是即使 reloadData 而不是 batchUpdate 也会导致崩溃
  • 在这种情况下,您如何以及何时重新加载,崩溃的原因是什么?
  • 我尝试在 controllerDidChange 上重新加载,但奇怪的是,崩溃是一样的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多