【问题标题】:Crash on UITableView endUpdates when moving last row in section移动部分中的最后一行时 UITableView endUpdates 崩溃
【发布时间】:2011-12-21 12:38:45
【问题描述】:

我有一个由 NSFetchedResultsController 支持的 UITableViewController。

我的 NSFetchedResultsController 根据布尔值将结果分为两个部分。

在后台线程中,数据源被更改,以便添加或删除行。 我的后台线程的 NSManagedObjectContext 正确合并,这种情况在大多数情况下都可以正常工作。当数据更改时行会发生变化,并在带有动画的部分之间移动。

但在一种情况下,我的应用程序因 EXC_BAD_ACCESS 而崩溃。 这种情况是当一个部分的最后一行被移动到另一个部分时。 (下面的堆栈跟踪)。崩溃发生在 objc_msgSend 但我使用的the normal debugging tips 没有返回任何有用的信息,我只是从gdb 收到“值无法转换为整数”。

NSFetchedResultsControllerDelegate 方法按以下顺序调用:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
     // ^ The parameters specify a deletion of the 1st section
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath;
     // ^ The parameters specify a moved row from the 1st section to the 1st section 
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;

查看 StackOverflow,许多遇到类似问题的人都在使用 NSArray 或 NSMutableArray 手动支持他们的 UITableViewController,他们只是没有在正确的时间更新数据源。在这种情况下,NSFetchedResultsController 正在处理返回的行数和节数,并且在调用 [tableView endUpdates] 时肯定会更新。

有没有人有任何调试技巧、指针或解决方案? This blog post 提示解决创建新部分的类似问题。

如果我无法按原样解决此问题,我有两种选择:

  1. 放弃对行的动画,只需调用[tableView reloadData]
  2. Switch to using NSArray backed storage for the UITableView 并希望一切顺利

更新

我已修改 Apple 示例核心数据应用程序“位置”以直接使用 NSFetchedResultsController 并重现了该问题。 I have uploaded the source code for this project。使用该项目重现问题。

  1. 只需点击几次 + 按钮并等待
  2. 每 5 秒就有一个项目移动到另一个类别。
  3. 当第一个类别即将被清空时,它会崩溃。

崩溃有时是关于为同一个单元格创建两个动画,就像有关该主题的其他 StackOverflow 问题一样。更常见的崩溃是如上文和下文所述。

参考

堆栈跟踪:

#0  0x31e0afbc in objc_msgSend ()
#1  0x32c11522 in -[_UITableViewUpdateSupport(Private) _computeRowUpdates] ()
#2  0x32c10510 in -[_UITableViewUpdateSupport initWithTableView:updateItems:oldRowData:newRowData:oldRowRange:newRowRange:context:] ()
#3  0x32c0f99e in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] ()
#4  0x32c0e66c in -[UITableView endUpdatesWithContext:] ()
#5  0x000088d6 in -[DraftHistoryController controllerDidChangeContent:] (self=0x3f6f1e0, _cmd=0x377ca29c, controller=0x3f716e0) at /Users/ataylor/Documents/Documents/Programming/iPhone/Drafter/Drafter/Drafter/DraftHistoryController.m:323
#6  0x3775a892 in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()

我的 NSFetchedResultsControllerDelegate 方法与 Apple 示例代码中的方法相同:

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


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

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

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

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            // Reloading the section inserts a new row and ensures that titles are updated appropriately.
            [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (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)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];     // <---- Crash occurs here
}

相关的UITableViewControllerDelegate方法如下:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

【问题讨论】:

    标签: objective-c ios core-data nsfetchedresultscontroller


    【解决方案1】:

    我快速浏览了一下。您修改后的 Locations 项目并没有让我崩溃,但它确实产生了核心数据异常。问题在于重新加载控制器中的部分:didChangeObject:。我将代码更改如下,并且在 iOS 4.3 和 iOS 5 上对我来说一切都很好(包括章节标题)。

    - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
    {
        UITableView *tableView = self.tableView;
    
        switch(type) {
    
            // other cases here
    
            case NSFetchedResultsChangeMove:
                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
    

    【讨论】:

    • 这并不能真正解决问题。删除移动的行并将它们重新插入其他位置不会导致相同的错误,但也会丢失行动画。
    【解决方案2】:

    我对修复添加新部分的问题的代码非常好奇,所以我决定弹出它来代替我当前的 NSFetchedResultsControllerDelegate 方法,它确实解决了我的问题。追溯,我发现Apple“Locations”示例中的这段代码在使用 NSFetchedResultsController 时会崩溃:

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            // Reloading the section inserts a new row and ensures that titles are updated appropriately.
            [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
            break;
    

    但以下代码完美运行:

        case NSFetchedResultsChangeMove:
            if (newIndexPath != nil) {
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationTop];
            }
            else {
                [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
            }
            break;
    

    我没有节标题会改变的情况,我不会继续调试来解决 Apple 的代码声称要解决节标题周围的问题。

    【讨论】:

      【解决方案3】:

      因为每个问题都需要一个快速的答案,所以这里有一个(基于 XJones 接受的答案)。与其他答案一样,神奇之处在于 .Move: delete and insert 而不是移动行。

      这是我的 NSFRC 委托 didChangeObject:

      func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
          switch type {
          case .Delete:
              if let deletePath = indexPath {
                  self.tableView.deleteRowsAtIndexPaths([deletePath], withRowAnimation: .None)
              }
              break
          case .Insert:
              if let insertPath = newIndexPath {
                  self.tableView.insertRowsAtIndexPaths([insertPath], withRowAnimation: .Fade)
              }
              break
          case .Update:
              if let updatePath = indexPath {
                  self.tableView.reloadRowsAtIndexPaths([updatePath], withRowAnimation: .None)
              }
              break
          case .Move:
              if let indexPath = indexPath, newIndexPath = newIndexPath {
                  self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
                  self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
              }
              break
          }
      }
      

      【讨论】:

        【解决方案4】:

        我在某个部分中删除了最后一行,导致崩溃加班。

        请确保您已实施 controller:didChangeSection:atIndex:forChangeType: 来处理部分删除。

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

        【讨论】:

          猜你喜欢
          • 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
          相关资源
          最近更新 更多