【发布时间】:2019-05-03 19:45:14
【问题描述】:
我使用的是相当标准的 NSTableView + CoreData + NSFetchedResultsController 设置,相关的视图控制器是 NSFetchedResultsControllerDelegate 来接收更改。以下是来自视图控制器的相关代码:
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")
}
我知道我应该能够以这种方式批量处理所有更新,即使删除了多行。但是,这会导致连续多次删除导致崩溃。
这是一个会话的日志输出,表最初有四行,所有这些都被删除:
tableViewBeginUpdates
Change type NSFetchedResultsChangeType for indexPath Optional([0, 2]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 4 rows
Change type NSFetchedResultsChangeType for indexPath Optional([0, 1]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 3 rows
Change type NSFetchedResultsChangeType for indexPath Optional([0, 0]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 2 rows
Change type NSFetchedResultsChangeType for indexPath Optional([0, 3]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 1 rows
最后一行导致崩溃:
2019-05-06 22:01:30.968849+0300 MyApp[3517:598234] *** Terminating app due to uncaught exception 'NSTableViewException', reason: 'NSTableView error inserting/removing/moving row 3 (numberOfRows: 1).'
前三个删除碰巧以“正确”的顺序报告(首先删除具有较大索引 [行号] 的行)。最后一行“乱序”到达,此时其他行似乎已经从 NSTableView 中消失了。
首先如何从上下文中删除对象:我正在使用推荐的最佳实践,即让两个托管对象上下文针对同一个 NSPersistentContainer 工作,一个用于 UI 在主线程中工作,一个用于后台/网络在后台工作。他们观察彼此的变化。当同步上下文从网络接收到一些更改、保存它们并将它们传播到视图上下文时会触发此崩溃,在应用程序的其他地方使用此方法:
@objc func syncContextDidSave(note: NSNotification) {
viewContext.perform {
self.viewContext.mergeChanges(fromContextDidSave: note as Notification)
}
}
我是否误解了如何使用获取的结果控制器委托?我认为 beginupdates/endupdates 调用确保“表视图模型”在它们之间不会改变?我应该怎么做才能消除崩溃?
【问题讨论】:
-
beginupdates/endupdates 对动画进行分组,而不是
removeRowss。如何从上下文中删除对象?anObject是否匹配indexPath? -
对象删除是由另一个
NSManagedObjectContext针对同一个NSPersistentCoordinator触发的。我在我的问题中添加了一些额外的信息。对象似乎是正确的。报告的对象与删除前的indexPath匹配。 -
批量处理但还是按顺序执行,当第一个被删除时,会改变第二个请求的索引,最终会出现索引越界问题。
-
@Digitalsa1nt 谢谢,这是有道理的。我是否应该通过处理批量更改来跟踪这些更改的索引,并为每个传入的更改调整索引?听起来可行,但我不敢相信我是世界上第一个不得不处理这个问题的人。我希望得到一个现成的(希望经过验证/测试的)食谱作为答案:D
-
@Digitalsa1nt 这些删除不是由用户触发的,而是由来自网络的某些东西(特别是 CloudKit)触发的。用户只是看到信息消失了(可能是因为这些项目是由决定删除他们创作的内容的其他人创作的)。
标签: core-data nsfetchedresultscontroller nstableview appkit