我有一个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提示要解决创建新部分的类似问题。
如果我不能解决这个问题,我有两个选择:
[tableView reloadData]
我修改了Apple示例Core Data应用程序“Locations”以直接使用NSFetchedResultsController并重现该问题。 I have uploaded the source code for this project。使用该项目重现该问题。
崩溃有时是关于为该主题创建与其他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];
}
答案 0 :(得分:6)
- (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;
}
}