Why does NSTableView crash when processing deleted rows as NSFetchedResultsControllerDelegate?

I am using a fairly standard setup of NSTableView + CoreData + NSFetchedResultsController, with the relevant view controller being NSFetchedResultsControllerDelegate to receive the changes. Here are the relevant bits of code from the view controller:


func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?){

   print("Change type \(type) for indexPath \(String(describing: indexPath)), newIndexPath \(String(

    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.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) {
    print("tableViewBeginUpdates")
    tableView.beginUpdates()
}


func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
    tableView.endUpdates()
    print("tableViewEndUpdates")
}


I understand that I should be able to batch all the updates this way, even if multiple rows are deleted. However, this causes a crash with multiple deletes in a row.


Here is the log output from a session, with the table initially having five rows, and all of them being deleted:


tableViewBeginUpdates
Change type NSFetchedResultsChangeType for indexPath Optional([0, 4]), newIndexPath nil
Change type NSFetchedResultsChangeType for indexPath Optional([0, 3]), newIndexPath nil
Change type NSFetchedResultsChangeType for indexPath Optional([0, 1]), newIndexPath nil
Change type NSFetchedResultsChangeType for indexPath Optional([0, 0]), newIndexPath nil
Change type NSFetchedResultsChangeType for indexPath Optional([0, 2]), newIndexPath nil


The last row causes a crash:


2019-05-03 22:33:13.361102+0300 MyApp[2104:168334] [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. NSTableView error inserting/removing/moving row 2 (numberOfRows: 1). with userInfo (null)


The first four deletes happen to be reported in the "right" order (rows with bigger indexes [row numbers] being deleted first). The last one arrives “out of order” and the other rows are seemingly already gone from NSTableView by this time.


Have I misunderstood how to work with the fetched results controller delegate? I thought that the beginupdates/endupdates calls make sure that the “table view model” does not change between them? What should I do to eliminate the crash?

Accepted Reply

Replies

I got a good response to this here: https://stackoverflow.com/questions/55976212/why-does-nstableview-crash-when-processing-deleted-rows-as-nsfetchedresultscontr