So one thing to keep in mind, NSManagedObjectContexts are fairly cheap to create. However, you will have the overhead of having to keep track of your child contexts - and especially implement guards to disallow cross context access of NSManagedObjects. So right off the bat, you should not be using any privateQueueConcurrencyType/Background Context for any user-facing interface. Mostly, operations performed on a background context aren't finalized until they're merged against the main persistent store. Basically you'll run into anomalous behavior if not crashes operating on objects that haven't persisted.
My recommendation is to keep using that background context for large transactions, but use either the main viewContext or a child context w/ mainQueueConcurrencyType as a read-only fetch view into the persistent store. Then with the background context, you can perform create/update transactions without blocking the UI.
Here's some snippets on how I've set up my background contexts. Side-note: you kinda have to use privateQueueConcurrencyType anyways if you want to support CloudKit+CoreData as I've discovered through testing.
Creating background context, do your usual context.perform & save
func newChildCreateContext(type: NSManagedObjectContextConcurrencyType = .privateQueueConcurrencyType, mergesChangesFromParent: Bool = true) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: type)
context.parent = self
context.name = "privateCreateContext"
context.transactionAuthor = appTransactionAuthorName
context.automaticallyMergesChangesFromParent = mergesChangesFromParent
return context
}
Observe the NSManagedObjectContextDidSave notification from NotificationCenter. Merge the changed objects and the main context should resolve everything.
updateChangeObserver = NotificationObserver(name: .NSManagedObjectContextDidSave, object: updateContext)
updateChangeObserver.onReceived = mergeUpdateObjects
private func mergeUpdateObjects(notification: Notification) {
DispatchQueue.main.async { [unowned self] in
self.contextDidSave(self.updateContext, note: notification)
}
}
func contextDidSave(_ context: NSManagedObjectContext, note: Notification) {
guard context === createContext || context === updateContext else { return }
context.perform {
context.mergeChanges(fromContextDidSave: note)
}
}
Signal to your FetchedResultsController refresh its fetchedObjects after the merge via some other NotificationCenter observer or something.
Refresh Fetched Objects on your main context controller via a NotificationCenter call or something after the merge. Changes should be reflected in the UI non-blocking.
func refreshFetchedObjects() {
fetchedResult.fetchedObjects?.forEach({ $0.objectWillChange.send() })
fetchedResult.managedObjectContext.refreshAllObjects()
}
Also another note, if you're adding a constant amount of objects - are you doing it through the new NSBatchInsertRequest operation? If you aren't already, I've found the new operation to be incredibly performant. You just have to make sure you merge the changes against the main context.
public func executeAndMergeChanges(using batchInsertRequest: NSBatchInsertRequest) throws {
batchInsertRequest.resultType = .objectIDs
let result = try execute(batchInsertRequest) as? NSBatchInsertResult
let changes: [AnyHashable: Any] = [NSInsertedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}