I have a multiple number of Core Data entities, all synced to the server. For testing I have built a function that will: 1. Delete all of the core data entities 2. Call APIs to refresh all data
// Next delete data in all entities (but not user)
for entity in CoreDataStack.sharedInstance.allEntities() {
if (entity != "User") {
if (DataAccess.deleteExisting(entityName: entity, inMoc: self.operationMOC)) {
print("Deleted OK: \(entity)")
} else {
print("ERROR deleting \(entity)")
}
}
}
....
// Save updates
self.operationMOC.performAndWait {
do {
try self.operationMOC.save()
} catch {
fatalError("ERROR: Failed to save operation moc: \(error)")
}
}
self.mainThreadMOC.performAndWait {
do {
try self.mainThreadMOC.save()
} catch {
fatalError("ERROR: Failed to save main moc: \(error)")
}
}
The problem is that the NSFetchedResultsController controlling my UI seems not to recognize the delete, giving an error when the "didChange" delegate method fires for the update
error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (14) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
I have split the refresh function into 2 parts so I can see that the delete is never recognized, hence there are too many UITableView rows when the update fires and tries to tableView.insertRows(at: ...). I have also tried direct updates to the MOC on my main thread - no luck
I have also put in an observer:
NotificationCenter.default.addObserver(self, selector: #selector(MessagesTab.dataChanged), name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: self.mainThreadMOC)
Which fires perfectly on delete and update, so Core Data is doing it's job.
Consequently, my question before I dump the NSFetchResults Controller and roll my own using notifications, is does anyone have any idea what I am doing wrong here? (either in code or expectations) I have been stumped on this for the past day so any advice would be gratefully received.
My NSFetchedResultsControllerDelegate didChange method:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
print(">>>> FRC Change Fired")
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .update:
let rowMessage = self.fetchedResultsController.object(at: indexPath!)
if let cell = self.tableView.cellForRow(at: indexPath!) as? InterestsListCell {
cell.configureWithData(rowMessage)
}
default:
print("Was not a update, delete or insert")
}
}
Many thanks to @JonRose This code will NOT cause the FRC to fire
// Delete all records for the entity
func deleteExisting(entityName: String, inMoc moc: NSManagedObjectContext) -> Bool {
do {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
try moc.execute(deleteRequest)
return true
} catch {
print("ERROR: deleting existing records - \(entityName)")
return false
}
}
Subsequently I have researched this and hope that it will help others - see WWDC 2015 session on Core Data where this slide was shown:
I have tried using .refreshAll() and .mergeChages, but the simples for me seemed to be to ditch my FRC and use NSManagedObjectContextDidSave notification