【发布时间】: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 提示解决创建新部分的类似问题。
如果我无法按原样解决此问题,我有两种选择:
- 放弃对行的动画,只需调用
[tableView reloadData] - Switch to using NSArray backed storage for the UITableView 并希望一切顺利
更新
我已修改 Apple 示例核心数据应用程序“位置”以直接使用 NSFetchedResultsController 并重现了该问题。 I have uploaded the source code for this project。使用该项目重现问题。
- 只需点击几次 + 按钮并等待
- 每 5 秒就有一个项目移动到另一个类别。
- 当第一个类别即将被清空时,它会崩溃。
崩溃有时是关于为同一个单元格创建两个动画,就像有关该主题的其他 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