Explanation
NSPersistentContainer
's instance methods performBackgroundTask(_:)
and newBackgroundContext()
are poorly documented.
No matter which method you call, in either case the (returned) temporary NSManagedObjectContext
is set up with privateQueueConcurrencyType
and is associated with the NSPersistentStoreCoordinator
directly and therefore has no parent
.
See documentation:
Invoking this method causes the persistent container to create and
return a new NSManagedObjectContext with the concurrencyType set to
privateQueueConcurrencyType. This new context will be associated with
the NSPersistentStoreCoordinator directly and is set to consume
NSManagedObjectContextDidSave broadcasts automatically.
... or confirm it yourself:
persistentContainer.performBackgroundTask { (context) in
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
}
let context = persistentContainer.newBackgroundContext()
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
Due to the lack of a parent
, changes won't get committed to a parent context
like e.g. the viewContext
and with the viewContext
untouched, a connected NSFetchedResultsController
won’t recognize any changes and therefore doesn’t update or call its delegate
's methods. Instead changes will be pushed directly to the persistent store coordinator
and after that saved to the persistent store
.
I hope, that I was able to help you and if you need further assistance, I can add, how to get the desired behavior, as described by you, to my answer. (Solution added below)
Solution
You achieve the behavior, as described by you, by using two NSManagedObjectContext
s with a parent-child relationship:
// Create new context for asynchronous execution with privateQueueConcurrencyType
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Add your viewContext as parent, therefore changes are pushed to the viewContext, instead of the persistent store coordinator
let viewContext = persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.perform {
// Do your work...
let object = backgroundContext.object(with: restaurant.objectID)
backgroundContext.delete(object)
// Propagate changes to the viewContext -> fetched results controller will be notified as a consequence
try? backgroundContext.save()
viewContext.performAndWait {
// Save viewContext on the main queue in order to store changes persistently
try? viewContext.save()
}
}
However, you can also stick with performBackgroundTask(_:)
or use newBackgroundContext()
. But as said before, in this case changes are saved to the persistent store directly and the viewContext
isn't updated by default. In order to propagate changes down to the viewContext
, which causes NSFetchedResultsController
to be notified, you have to set viewContext.automaticallyMergesChangesFromParent
to true
:
// Set automaticallyMergesChangesFromParent to true
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.performBackgroundTask { context in
// Do your work...
let object = context.object(with: restaurant.objectID)
context.delete(object)
// Save changes to persistent store, update viewContext and notify fetched results controller
try? context.save()
}
Please note that extensive changes such as adding 10.000 objects at once will likely drive your NSFetchedResultsController
mad and therefore block the main queue
.