【问题标题】:Assertion failure in -[UITableView _endCellAnimationsWithContext:] with NSFetchedResultsController使用 NSFetchedResultsController 在 -[UITableView _endCellAnimationsWithContext:] 中断言失败
【发布时间】:2013-06-03 21:13:27
【问题描述】:

我有 2 个托管对象上下文:(1) 创建为 NSMainQueueConcurrencyType,供 UI/主线程使用;(2) 创建为 NSPrivateQueueConcurrencyType,供网络使用。这两个上下文都进入持久存储(即,我没有使用父/子上下文)。

对于视图控制器,我使用 UITableViewControllerNSFetchedResultsController,它使用第一个 UI 托管对象上下文。

我正在通过观察NSManagedObjectContextDidSaveNotification 将第二个托管对象上下文中的更改合并到第一个上下文中。

应用程序正常工作,直到它处理导致插入新对象的网络响应在第二个上下文中删除现有对象。保存第二个上下文时,NSManagedObjectContextDidSaveNotification 会触发并将更改合并到第一个上下文中。调用NSFetchedResultsController 的委托方法并向表中添加了新行,但表示已删除对象的行*没有删除。

如果我尝试在表视图上执行其他操作,例如重新加载表或更新其他对象,我会在控制台日志中得到以下断言:

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:],
/SourceCache/UIKit/UIKit-2380.17/UITableView.m:1070

CoreData: error: Serious application error.  An exception was caught
from the delegate of NSFetchedResultsController during a call to -
controllerDidChangeContent:.  Invalid update: invalid number of rows in
section 0.  The number of rows contained in an existing section after the
update (6) must be equal to the number of rows contained in that section
before the update (7), plus or minus the number of rows inserted or deleted
from that section (6 inserted, 6 deleted) and plus or minus the number of rows
moved into or out of that section (0 moved in, 0 moved out). with 
userInfo (null)

通常,如果您在使用 UITableView 的批量更新方法时忘记更新模型对象,则会收到此错误,但在这种情况下,NSFetchedResultsController 正在完成所有工作。我的委托方法是样板:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
  [self.tableView beginUpdates];
}

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

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
  NSLog(@"    didChangeObject type=%d indexPath=%@ newIndexPath=%@", type, indexPath, newIndexPath);

  UITableView *tableView = self.tableView;

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
  [self.tableView endUpdates];
}

我的 UITableViewDataSource tableView:cellForRowAtIndexPath 方法是:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *CellIdentifier = @"Cell";

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
      cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
      cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  }

  [self configureCell:cell atIndexPath:indexPath];

  return cell;
}

configureCell: 是:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
  Event *event = (Event *)[self.fetchedResultsController objectAtIndexPath:indexPath];
  cell.textLabel.text = [[event valueForKey:@"timeStamp"] description];

  NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Owner"];
  NSArray *objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
  cell.detailTextLabel.text = [objects.lastObject name]; // simplified
}

【问题讨论】:

    标签: ios objective-c core-data


    【解决方案1】:

    如果您在准备单元格以响应tableView:cellForRowAtIndexPath: 时使用NSFetchRequest,那么NSFetchedResultsController 会非常混乱。如果你不执行NSFetchRequest,一切都很好。但是,如果您这样做,它会触发NSFetchedResultsController 执行进一步的更改通知,这对UITableView 不利。

    解决方法是在您的 NSFetchRequest 上设置 includesPendingChanges = NO

    我已经打开了一个关于此的雷达问题——问题 ID 14048101——带有详细的示例和示例应用程序。此错误在 iOS 5.1、6.0 和 6.1 上重现。

    在我的示例应用程序中,我将日志记录添加到 Xcode 的 CoreData 模板以记录 NSFetchedResultsController 委托方法的进入/离开。当我在网络上下文中插入 + 删除对象时,日志显示:

    01: =>(之前)mergeChangesFromContextDidSaveNotification 02:=>(输入)controllerWillChangeContent 计数=4 03:(输入)controllerDidChangeContent count=5

    此时,一切都很好。已调用controllerDidChangeContent: 处理1 插入,该插入调用[tableView endUpdates],该调用调用tableView:cellForRowAtIndexPath:,该调用调用configureCell:atIndexPath:

    06: => (enter) 配置第 0 行的单元格

    此时,configureCell:atIndexPath: 创建了一个NSFetchRequest 并调用了[self.managedObjectContext executeFetchRequest:error:]——这就是糟糕的开始。执行此获取请求会在插入处理完成之前触发上下文中剩余更改的处理(1 次删除和 3 次更新)(我们在第 05 行输入controllerDidChangeContent:,直到第 16 行才离开)。

    07: =>(输入)controllerWillChangeContent count=5 08:(输入)controllerDidChangeContent count=4

    此时,框架正在重新调用controllerDidChangeContent:

    14:

    此时,您可以在 UI 中看到:(1) 添加了一个新单元格,(2) 更新了 3 个单元格,以及 (3) 删除的单元格仍然可见,这是错误的。

    p>

    在 UI 中进行进一步操作后,我通常会收到 Assertion failure 或发送到无效对象异常的消息。

    我的示例应用程序可在 https://github.com/peymano/CoreDataFetchedResultsController 获得

    【讨论】:

    • 就是这样!在查看了与此错误相关的许多不同问题后,我得出了这个答案。这是唯一一个有助于了解我的问题的原因。如果你们中的任何人有这个问题,要么使用提到的锻炼,或者你可以通过使用关系而不是 configurecell 方法中的单独获取请求来解决这个问题。这真的帮助了我,谢谢!
    • 你能解释更多关于配置单元方法中的“使用关系”吗?
    猜你喜欢
    • 1970-01-01
    • 2012-04-25
    • 2014-11-19
    • 1970-01-01
    • 1970-01-01
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多