Я использую довольно стандартную настройку 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, один для работы пользовательского интерфейса в основном потоке, и один для фоновой / сетевой работы в фоновом режиме. Они следят за изменениями друг друга. Этот сбой вызывается, когда контекст синхронизации получает некоторые изменения из сети, сохраняет их, и они распространяются для просмотра контекста с помощью этого метода в другом месте приложения:
@objc func syncContextDidSave(note: NSNotification) {
viewContext.perform {
self.viewContext.mergeChanges(fromContextDidSave: note as Notification)
}
}
Я неправильно понял, как работать с полученным делегатом контроллера результатов? Я думал, что вызовы beginupdates / endupdates удостоверяются, что «модель табличного представления» не изменяется между ними? Что я должен сделать, чтобы устранить аварию?