Can I set the parent context of my ManagedObjectContext to a ManagedObjectContext with a different concurrency type? For example:
backgroundManagedObjectContext_ = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [backgroundManagedObjectContext_ setPersistentStoreCoordinator:coordinator]; managedObjectContext_ = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [managedObjectContext_ setParentContext:backgroundManagedObjectContext_];
My goal is to (hopefully) get a fast save for managedObjectContext_ (as it only needs to save things to the parent in-memory context) and then have the backgroundManagedObjectContext_ do a save on its own queue. Unless I happen to need to do another save from the "foreground" queue before the background one saves my user should never see long save times.
I'm running into what look like deadlocks, but I'm not sure if they are related to this, or if my problem is elsewhere.
Details for the one place where I can more or less reliably produce the deadlock:
I have a managed object context for the main thread, it is backed by a managed object context with a private queue concurrency type, and that one has a persistent store.
I have a handful of entity types (about 5), each with one or two bi-directional relationships to another entity.
My app wants to use iCloud (I have that code dialed for these tests), so it can't ship with a pre-populated database, it needs to build it when it detects an empty one.
So when I see an empty database (this is checked on the main thread) I add around 4 or 8 of all but one of the entity types, and that last one gets about 100 (all of the adds happen on the main thread). The main thread does a saveContext. After that completes (with no errors) it asks the other managed object context to run a block that also does a saveContext. This saveContext is definitely a deadlock participant. This is also the ONLY stuff done with the "background" NSManagedObjectContext at all.
The exact control flow is a little murky after this, as a NSFetchedResultsController (all of a given entity type (which has ~3 members) with a simple sort, and a batch size of 20 or so) drive the next round of Core Data activity, but there is call from the TableViewController asking how many items it needs to manage, which is "how many results does the fetched results controller have". That call is the other side of the deadlock. (all this is in the main thread)
You cannot pass NSManagedObjects between multiple contexts, but you can pass NSManagedObjectIDs and use them to query the appropriate context for the object represented by that ID.
To use Core Data in a multithreaded environment, ensure that: Managed object contexts are bound to the thread (queue) that they are associated with upon initialization. Managed objects retrieved from a context are bound to the same queue that the context is bound to.
If you need to pass a managed object from one thread to another, you use a managed object's objectID property. The objectID property is of type NSManagedObjectID and uniquely identifies a record in the persistent store. A managed object context knows what to do when you hand it an NSManagedObjectID instance.
Most apps need just a single managed object context. The default configuration in most Core Data apps is a single managed object context associated with the main queue. Multiple managed object contexts make your apps harder to debug; it's not something you'd use in every app, in every situation.
From my experience, this is not necessary as long as both contexts implement either the NSMainQueueConcurrencyType
or the NSPrivateQueueConcurrencyType
. The important thing to remember is that when interacting with managed object contexts across multiple threads, all messages sent to the context must be sent via either -performBlock:
or performBlockAndWait:
.
In a recent project, I had a parent NSManagedObjectContext
that backed an NSFetchedResultsController
that was created with the NSMainQueueConcurrencyType
. From there I created an NSManagedObjectContext
with the NSPrivateQueueConcurrencyType
and set the context with the NSMainQueueConcurrencyType
as the parent. Now my child context could contain discardable edits when adding a new object that would eventually end up in the table view backed by the NSFetchedResultsController
which is backed by the parent context. When I was ready to save my edits and bubble them up to the parent context, the code looked like this.
// Save the child context first [childContext performBlock:^{ NSError *error = nil; [childContext save:&error]; // Save the changes on the main context [parentContext performBlock:^{ NSError *parentError = nil; [parentContext save:&parentError]; }]; }];
Now I can't say with certainty that this is the correct way to do this because the documentation on this is pretty sparse at the moment. It might be helpful if you could show some code that you believe is causing deadlocks. If you're in the developer program, you may want to check out the WWDC session videos regarding Core Data from this year. I believe there are two, one for Mac OS X and one for iOS, that basically touch on the same ideas but each contain unique information as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With