在CoreData更新期间替换UICollectionView的NSFetchedResultsController时崩溃

时间:2016-07-18 09:13:59

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

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

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;

}

1 个答案:

答案 0 :(得分:0)

虽然您的批处理代码看起来很聪明和有效,但很难确保批量更改是有说服力的。如果插入或删除任何部分,或者移动了任何项目,那么它非常非常难以确保所有其他索引路径都正确并且引用相应的项目。

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

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

你没有为你的FRC更改显示任何代码,我想这是无关的。切换时一定要重装。

相关问题