Core data 作为NSFetchedResultsControllerDelegate处理删除的行时,为什么NSTableView会崩溃?
我使用的是相当标准的NSTableView+CoreData+NSFetchedResultsController设置,相关的视图控制器为NSFetchedResultsController委托以接收更改。以下是来自视图控制器的相关代码位:Core data 作为NSFetchedResultsControllerDelegate处理删除的行时,为什么NSTableView会崩溃?,core-data,nsfetchedresultscontroller,nstableview,appkit,Core Data,Nsfetchedresultscontroller,Nstableview,Appkit,我使用的是相当标准的NSTableView+CoreData+NSFetchedResultsController设置,相关的视图控制器为NSFetchedResultsController委托以接收更改。以下是来自视图控制器的相关代码位: func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: Inde
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?){
print("Change type \(type) for indexPath \(String(describing: indexPath)), newIndexPath \(String(describing: newIndexPath)). Changed object: \(anObject). FRC by this moment has \(String(describing: self.frc?.fetchedObjects?.count)) objects, tableView has \(self.tableView.numberOfRows) rows")
switch type {
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath.item], withAnimation: .effectFade)
}
case .delete:
if let indexPath = indexPath {
tableView.removeRows(at: [indexPath.item], withAnimation: .effectFade)
}
case .update:
if let indexPath = indexPath {
let row = indexPath.item
for column in 0..<tableView.numberOfColumns {
tableView.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: column))
}
}
case .move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.removeRows(at: [indexPath.item], withAnimation: .effectFade)
tableView.insertRows(at: [newIndexPath.item], withAnimation: .effectFade)
}
@unknown default:
fatalError("Unknown fetched results controller change result type")
}
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
print("tableViewBeginUpdates")
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
print("tableViewEndUpdates")
}
最后一行导致崩溃:
2019-05-06 22:01:30.968849+0300 MyApp[3517:598234]***由于未捕获的异常“NSTableViewException”而终止应用程序,原因:“NSTableView错误插入/删除/移动第3行(numberOfRows:1)。”
前三次删除碰巧是按“正确”的顺序报告的(首先删除索引较大[行号]的行)。最后一行出现“无序”,其他行此时似乎已经从NSTableView中消失
首先如何从上下文中删除对象:我正在使用建议的最佳实践,即让两个托管对象上下文在同一个NSPersistentContainer上工作,一个用于主线程中的UI工作,另一个用于后台/网络工作。他们互相观察对方的变化。当同步上下文从网络接收到一些更改并保存这些更改,然后将这些更改传播到视图上下文时,会触发此崩溃,在应用程序的其他位置使用此方法:
@objc func syncContextDidSave(note: NSNotification) {
viewContext.perform {
self.viewContext.mergeChanges(fromContextDidSave: note as Notification)
}
}
我是否误解了如何使用获取的结果控制器委托?我认为BeginUpdate/endupdates调用可以确保“表视图模型”不会在它们之间发生变化?我应该做些什么来消除崩溃?希望UITableView上有关批量删除操作的官方文档能对您有所帮助 在下面的示例中,删除操作将始终首先运行,从而推迟删除操作,但其目的是在开始和结束之间同时提交所有操作,以便UITableView可以为您完成繁重的工作
- (IBAction)insertAndDeleteRows:(id)sender {
// original rows: Arizona, California, Delaware, New Jersey, Washington
[states removeObjectAtIndex:4]; // Washington
[states removeObjectAtIndex:2]; // Delaware
[states insertObject:@"Alaska" atIndex:0];
[states insertObject:@"Georgia" atIndex:3];
[states insertObject:@"Virginia" atIndex:5];
NSArray *deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:4 inSection:0],
nil];
NSArray *insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
[NSIndexPath indexPathForRow:5 inSection:0],
nil];
UITableView *tv = (UITableView *)self.view;
[tv beginUpdates];
[tv insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[tv deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tv endUpdates];
// ending rows: Alaska, Arizona, California, Georgia, New Jersey, Virginia
}
此示例从数组中删除两个字符串(及其
并将三个字符串插入数组(沿
以及相应的行)。下一节,排序
操作和索引路径,解释行的特定方面(或
节)插入和删除行为
这里的关键是在开始和结束更新调用之间同时传递所有删除索引。如果先存储索引,然后传入,那么就会遇到我在评论中提到的情况,即索引开始抛出越界异常
苹果和上面标题下的示例:“批量插入和删除操作的示例”
希望这有助于为您指明正确的方向。从fetchedResultsController更新要比Apple文档说明的更困难。当同时存在移动和插入或移动和删除时,您共享的代码将导致此类错误。对于您的案例来说,这似乎不是正在发生的事情,但是这个设置也会修复它
indexPath
是应用删除和插入之前的索引newIndexPath
是应用删除和插入后的索引
对于更新,您不关心它在插入和删除之前的位置-仅在插入和删除之后-因此使用newindepath
而不是indepath
。这将修复当您同时更新和插入(或更新和删除)单元格而单元格未按预期更新时可能发生的错误
对于move
,代表要说明它在插入之前从何处移动,以及在插入和删除之后应该插入到何处。当您进行移动和插入(或移动和删除)时,这可能是一个挑战。您可以通过将来自controller:didChangeObject:atIndexPath:forChangeType:newindexath:
的所有更改保存到三个不同的数组中,插入、删除和更新来修复此问题。当您得到一个move
时,在insert数组和delete数组中为它添加一个条目。在controllerDidChangeContent:
中,按降序排列删除数组,按升序排列插入数组。然后应用更改—首先删除,然后插入,然后更新。这将修复在移动和插入(或移动和删除)的同时发生的崩溃
我无法解释为什么你有一个无序删除。在我的测试中,我总是看到删除服务是降序的,插入服务是升序的。不过,此设置也会解决您的问题,因为有一个步骤可以对删除进行排序
如果有节,则还可以将节更改保存在数组中,然后按以下顺序应用更改:删除(降序)、节删除(降序)、节插入(升序)、插入(升序)、更新(任意顺序)。无法移动或更新节
总结:
- 按降序对行进行排序
- 排序部分删除降序
- 按升序排序
- 按升序排序行插入
BeginUpdate/endupdates对动画进行分组,而不是
removeRows
s。如何从上下文中删除对象?anObject
是否匹配indepath
?对象删除由另一个NSManagedObjectContext
再次触发
- (IBAction)insertAndDeleteRows:(id)sender {
// original rows: Arizona, California, Delaware, New Jersey, Washington
[states removeObjectAtIndex:4]; // Washington
[states removeObjectAtIndex:2]; // Delaware
[states insertObject:@"Alaska" atIndex:0];
[states insertObject:@"Georgia" atIndex:3];
[states insertObject:@"Virginia" atIndex:5];
NSArray *deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:4 inSection:0],
nil];
NSArray *insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
[NSIndexPath indexPathForRow:5 inSection:0],
nil];
UITableView *tv = (UITableView *)self.view;
[tv beginUpdates];
[tv insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[tv deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tv endUpdates];
// ending rows: Alaska, Arizona, California, Georgia, New Jersey, Virginia
}