移动部分中的最后一行时UITableView endUpdates崩溃

时间:2011-11-03 20:19:34

标签: objective-c ios core-data nsfetchedresultscontroller

我有一个UITableViewController,它由NSFetchedResultsController支持。

我的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. 即可
  3. Switch to using NSArray backed storage for the UITableView并希望获得最佳
  4. 更新

    我修改了Apple示例Core Data应用程序“Locations”以直接使用NSFetchedResultsController并重现该问题。 I have uploaded the source code for this project。使用该项目重现该问题。

    1. 只需点击+按钮几次,然后等待
    2. 每5秒将一个项目移动到另一个类别。
    3. 当第一个类别即将被清空时,它会崩溃。
    4. 崩溃有时是关于为该主题创建与其他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];
      }
      

4 个答案:

答案 0 :(得分:6)

我快速看了一下。您修改后的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;
    }
}

答案 1 :(得分:4)

我对修复添加新部分的问题的代码非常好奇,所以我决定用它来代替我当前的NSFetchedResultsControllerDelegate方法,它确实解决了我的问题。通过追踪,我发现这个代码来自Apple&#34; Locations&#34;使用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代码声称修复部分标题的问题。

答案 2 :(得分:1)

因为每个问题都需要快速回答,这是一个问题(根据XJones接受的答案)。与其他答案的情况一样,魔术在.Move:删除和插入而不是移动行。

这是我的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
    }
}

答案 3 :(得分:0)

我正在加速崩溃我删除了一节中的最后一行。

请确保您已实施 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;
    }
}