Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads
Asked Answered
T

2

36

iOS 5 introduced a new way to quickly fetch data on a background thread by initializing the MOC using NSPrivateQueueConcurrencyType and then doing the fetch in performBlock:

One of the rules of thumb of Core Data has been that you can not share a managed object between threads/queues. Is it still the case with performBlock:? Is the following:

[context performBlock:^{
    // fetch request code

    NSArray *results = [context executeFetchRequest:request error:nil];

    dispatch_async(dispatch_get_main_queue(), ^(void) {
        Class *firstObject = [results objectAtIndex:0];
        // do something with firstObject
    });
}];

still unacceptable since I'm sharing my results array/objects between the bg queue and the main queue? Do I still need to use the managed object IDs to do that?

Terraterrace answered 26/12, 2011 at 17:45 Comment(0)
G
66

When you use NSPrivateQueueConcurrencyType you need to do anything that touches that context or any object belonging to that context inside the -performBlock: method.

Your code above is illegal since you're passing those objects back to the main queue. The new API helps you in solving this, though: You create one context that's associated with the main queue, i.e. with NSMainQueueConcurrencyType:

// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];

// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
    NSArray *results = [backgroundMOC executeFetchRequest:request error:nil];
    for (NSManagedObject *mo in results) {
        NSManagedObjectID *moid = [mo objectID];
        [mainMOC performBlock:^{
            NSManagedObject *mainMO = [mainMOC objectWithID:moid];
            // Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
        }];
    }
}];

This gets less confusing if you move the inner [mainMOC performBlock:] call into its own method. You may also want to pass an array of object IDs back to the main thread's context in stead of executing a block for each object ID. It depends on your needs.

Germen answered 27/12, 2011 at 16:37 Comment(7)
What is the source of this information? This is certainly what I would expect to be the case, but Apple's documentation only states that you have to do MOC operations in the block. It is not explicit about MOs created in the block.Ambages
MOs belong to the context. They are not thread safe and hence must only be touched inside the queue of the context they belong to. It's not crystal clear from the documentation, but that's what it tried to say. I've got this info from the Core Data team.Germen
Where is the documentation? Anyone have a link?Punctuality
@DanielSkinner The best documentation I've seen so far is the WWDC 2011 "What's new in core data" session. It's far from comprehensive though.Nina
In the code example above, should the backgroundMOC be made the parent of the mainMOC? i.e. [mainMOC setParentContext:backgroundMOC];Die
In the WWDC 2012 presentation called "Core Data Best Practices" at about 12:30 it says "Also, okay to retain MOs but not look or use outside block" They recommend passing ObjectIDs in between blocks.Beyond
The developer URLs frequently change, so a link isn't going to be very helpful, but Googling "Concurrency with Core Data" should give you the documentation emphasizing that thread confinement of managed objects is the best approach to concurrency.Gilmagilman
N
3

As Daniel Eggert explains, this is definitely still the case. The exception is for NSMainQueueConcurrencyType, where you can also use the managed object context and objects safely on the main thread (as well as from other threads via the performBlock mechanism). The usefulness of this cannot be understated!

iOS 5 also introduced the concept of parent contexts, which also hugely simplify background operations and remove the need to worry about using notifications to propogate changes between threads.

The WWDC 2012 video "Session 214 - Core Data Best Practices" goes into a lot more detail on both subjects and is very comprehensive. The video is essential viewing for anyone using Core Data.

Nina answered 5/8, 2012 at 8:11 Comment(3)
As far as I can tell, NSMainQueueConcurrencyType is equivalent to creating an NSManagedObjectContext from the main thread, and is NOT any more thread safe than any other context. Where did you find that you can use it "from other threads via the performBlock mechanism"?Benitobenjamen
@Benitobenjamen In the WWDC 2012 video "Session 214 - Core Data Best Practices". -[NSManagedObjectContext performBlock:] is the exact mechanism I'm referring to.Nina
It's in the docs for NSManagedOBjectContext as well: "If your code is executing on the main thread, you can invoke methods on the main queue style contexts directly instead of using the block based API."Techno

© 2022 - 2024 — McMap. All rights reserved.