Core Data: parent context blocks child

给你一囗甜甜゛ 提交于 2019-11-30 15:14:22

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.

Timothy Sanders

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 background runQuery 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.

andrewbuilder

I suspect your problem is twofold, in that, even though you are doing for test purposes, your

lengthy core data operation

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 NSManagedObjectContexts.

I recommend you follow the steps outlined in this answer.

In addition, you can create an additional MOC specifically to manage your runQuery...

discreteMOC = NSManagedObjectContext(concurrencyType:NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
discreteMOC?.parentContext = backgroundMOC //MOCprivate in my previous answer. 

Hope this helps.

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.

lazy var managedObjectContext: NSManagedObjectContext = {
    let parentContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    parentContext.persistentStoreCoordinator = self.persistentStoreCoordinator

    let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.parentContext = parentContext
    return managedObjectContext
}()

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!