I'm doing some background processing in an app with core data. The background processing is done on a child managedObjectContext. Context initialization:
appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
// the moc in appDelegate is created with .MainQueueConcurrencyType
mainThreadMOC = appDelegate.managedObjectContext!
backgroundMOC = NSManagedObjectContext(concurrencyType:NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
backgroundMOC?.parentContext = mainThreadMOC
Background processing is done in the following method:
// download all new transaction log entries
func syncItems() {
... set up the query object for parse
let moc = CoreDataStore.sharedInstance.backgroundMOC
// perform download
moc?.performBlock( {
self.runQuery(query) // Download stuff und do some core data work
})
}
The debugger shows that all work inside the block is indeed in a background thread.
When I call this function from the main thread and immediately block the main thread (for test purpose) with a lengthy core data operation, I see that the background thread stops and only continues execution when the main thread is idle.
// this is called from a view controller in the main thread
syncItems() // should start to work in background
for i in 0...200 {
// do some core data work in main thread
}
// syncItems starts to work after the blocking for-loop ends.
Why is that happening?
Regarding parent contexts, unfortunately the last time I checked the documentation was fairly spartan, and online you will find that nearly every single person has inverted the flow. There was one WWDC session on Core Data back in 2011 or so where everything was made clear, and if you look at the API carefully, it will start to make sense.
The child is NOT the background context - it is the main context. The child is the context you are generally interacting with. The parent is the background context.
The child pushes changes to its parent. That is why the parent has a persistentStoreCoordinator, but the child instead has a parentContext (it does NOT need a persistentStoreCoordinator).
So the child is your main context, on the main (UI) queue. It saves changes up into its parent. This happens in memory (fast - leaving the UI as responsive as possible).
The parent context then saves its changes into the persist store via its persistentStoreCoordinator. That is why the parent is on a private queue.
There are other optimizations, such as setting the .undoManager to nil, but this general architecture works flawlessly for backgrounding saves.
You probably also want to put in a save method that receives a completion block/closure, immediately save on your child queue (which only saves into the parent, as mentioned), then call the parentContext's performBlock method in which you will have it save (on its private queue) into the underlying persistent store (comparatively slow, but now non-blocking), and then call your completion block/closure (which you either already set up using GCD to run back on the main queue, or else you call back to the main queue from there in the parent's performBlock method.
Everything falls into place perfectly fine when you don't invert the architecture. I'm not sure how it got started online, but it's a nearly universal mistake.
Best of luck.
Don’t use a parent-child context setup.
Parent-child context are not a good approach for just about anything. Just use a simple stack: two contexts with one shared persistent store coordinator.
Parent-child contexts just add a lot of confusion without giving you much of anything. It’s a pretty misunderstood concept. I wish people like mzarra would f***ing stop advocating that setup. It’s a disservice to the community.
If your background context is a child context of your main context, you will have to lock both the main and the background context, whenever the background context needs to save. This blocks the UI. And you’ll have to lock the UI a 2nd time to propagate those changes from the UI into the PSC. If you use a background context, you will have to merge changes into the main context, but you'll only do work for those objects that are currently registered with that context. If you added new objects, or updated / deleted objects that are not currently registered (referenced) that’s basically a no-op.
I suspect your problem is twofold, in that, even though you are doing for test purposes, your
on the main thread is blocking the UI, and by your definition your
backgroundMOC?.parentContext = mainThreadMOC
.I'd recommend creating a more robust structure for your multiple
NSManagedObjectContext
s.I recommend you follow the steps outlined in this answer.
In addition, you can create an additional MOC specifically to manage your
runQuery
...Hope this helps.
When you say in the comment "do some core data work in main thread" is that work accessing
mainThreadMOC
?It sounds like perhaps the main thread work is locking something that
runQuery
needs to access.Try changing the testing busy-work that blocks the main thread to something that doesn't access Core Data (
NSThread.sleepForTimeInterval
should do) and see if that lets the backgroundrunQuery
work.If that's the problem you'll need to refactor the main thread work into something that doesn't block whatever is happening in
runQuery
.