ios 核心数据:确保对象已从数据源中删除

ios Core Data: make sure object is deleted from data source

更新:在评论中有人指出我不必要地分派到主线程。在删除调度和不必要的 begin/end updates 之后,现在当我尝试删除一个单元格时,它会调用 didChangeObject 和大小写 NSFetchedResultsChangeUpdate(而不是 NSFetchedResultsChangeDelete),它会调用 configureCell.

导致程序崩溃的行是下面方法中的CollectedLeaf* theCollectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];。崩溃日志是 no section at index 20 in sections list.

- (void)configureCell:(SpeciesCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    CollectedLeaf* theCollectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];
    [cell setCollectedLeaf:theCollectedLeaf];
}

每次我从我的 table 中滑动删除一个单元格时,我都会收到 Invalid update: invalid number of rows in section 错误。我在 controllerDidChangeContent 中的 [_table endUpdates] 处专门获取了断点。

在许多 SO 帖子中,出现此错误的原因是在从 table 中删除行之前,对象尚未从数据源中删除。我从commitEditingStyle中的数据源中删除了对象,它在didChangeObject中的deleteRowsAtIndexPaths之前被调用。

尽管我下了订单,但我仍然收到 Invalid update 这一事实让我认为我没有 properly/successfully 从数据源中删除它。我还是新手 iOS - 如何确保从我的核心数据中删除对象?

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [_table beginUpdates];
}

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

        case NSFetchedResultsChangeInsert:
            [_table beginUpdates];

            [_table insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
            [_table endUpdates];
            break;

        case NSFetchedResultsChangeDelete:
            NSLog(@"delete called");
            [_table deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationFade];


            break;

        case NSFetchedResultsChangeUpdate:
           // [self configureCell:[_table cellForRowAtIndexPath:indexPath]
                   // atIndexPath:indexPath];
            [_table reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            NSLog(@"fetched update");
            break;

        case NSFetchedResultsChangeMove:
            [_table deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
            [_table insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [_table insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [_table deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeMove:
            break;
        case NSFetchedResultsChangeUpdate:
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [_table endUpdates];
}
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(_segmentedControl.selectedSegmentIndex == 1) {
        NSLog(@"index path at editingStyleForRowAtIndexPath %@", indexPath);
        return UITableViewCellEditingStyleDelete;
    }
    return NULL;
}

- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
    // [super tableView: tableView didEndEditingRowAtIndexPath:indexPath];
    if(_segmentedControl.selectedSegmentIndex == 1) {
        for (UITableViewCell *cell in [_table visibleCells]) {
            for (UIView *subview in cell.contentView.subviews)
                [subview.layer removeAllAnimations];
        }
    }
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    /*Only allow deletion for collection table */
    if(_segmentedControl.selectedSegmentIndex == 1) {
        if (editingStyle == UITableViewCellEditingStyleDelete)
        {
            NSLog(@"index path at commitediting style %@", indexPath);
            CollectedLeaf* collectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];
            LeafletPhotoUploader * leafletPhotoUploader = [[LeafletPhotoUploader alloc] init];
            leafletPhotoUploader.collectedLeaf = collectedLeaf;

            if([LeafletUserRegistration isUserRegistered]) {
                [leafletPhotoUploader deleteCollectedLeaf:collectedLeaf delegate:self];
            }

            // Delete the managed object for the given index path
            NSManagedObjectContext *context = [collectionFetchedResultsController managedObjectContext]; 
                [context deleteObject:[collectionFetchedResultsController objectAtIndexPath:indexPath]];

            // Save the context.
            NSError *error;
            if (![context save:&error])
            {
                NSLog(@"Failed to save to data store: %@", [error localizedDescription]);
                NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
                if(detailedErrors != nil && [detailedErrors count] > 0)
                {
                    for(NSError* detailedError in detailedErrors)
                    {
                        NSLog(@"  DetailedError: %@", [detailedError userInfo]);
                    }
                }
                else
                {
                    NSLog(@"  %@", [error userInfo]);
                }
            }
        }
    }
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"speciesCell";

    SpeciesCell* speciesCell = (SpeciesCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [self configureCell:speciesCell atIndexPath:indexPath];
    speciesCell.labelCheckMark.backgroundColor = [UIColor blackColor];

    return speciesCell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    id <NSFetchedResultsSectionInfo> sectionInfo = [[collectionFetchedResultsController sections] objectAtIndex:section];
    if([sectionInfo numberOfObjects] == 0){
        UILabel *noDataLabel         = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, tableView.bounds.size.height)];
        noDataLabel.text             = @"Press the Snap It! tab to start collecting!";
        //Start your collection by tapping on the Snap It! tab.";
        noDataLabel.textColor        = [UIColor whiteColor];
        noDataLabel.textAlignment    = NSTextAlignmentCenter;
        tableView.backgroundView = noDataLabel;
        tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    }
    return [sectionInfo numberOfObjects];
}

This post saved me

objectAtIndexPath 有很多问题,只能在边界检查中调用,所以我现在使用嵌套 if 语句

中的核心数据对象完成我的所有工作
    CollectedLeaf* theCollectedLeaf = nil;
    if ([[collectionFetchedResultsController sections] count] > [indexPath section]){
        id <NSFetchedResultsSectionInfo> sectionInfo = [[collectionFetchedResultsController sections] objectAtIndex:[indexPath section]];
        if ([sectionInfo numberOfObjects] > [indexPath row]){
            theCollectedLeaf = [collectionFetchedResultsController objectAtIndexPath:indexPath];

        //do whatever else I need, delete the object, etc
        }

    }

如链接 post 所述,

"The reason you are getting an exception is that the state of the NSFetchedResultsController is getting out of sync with the state of the tableview. When your crash happens the tableView just asked your delegate methods (numberOfSectionsInTableView and tableView:numberOfRowsInSection: for the number of rows and sections. When it asked that, the NSFetchedResultsController had data in it, and gave positive values (1 section, 3 rows). But in-between that happening and your crash, the entities represented by the NSFetchedResultsController were deleted from the NSManagedObjectContext. Now cellForRowAtIndexPath: is being called with an outdated indexPath parameter."