Core Data multi thread application

42,903

Solution 1

The Apple Concurrency with Core Data documentation is the place to start. Read it really carefully... I was bitten many times by my misunderstandings!

Basic rules are:

  1. Use one NSPersistentStoreCoordinator per program. You don't need them per thread.
  2. Create one NSManagedObjectContext per thread.
  3. Never pass an NSManagedObject on a thread to the other thread.
  4. Instead, get the object IDs via -objectID and pass it to the other thread.

More rules:

  1. Make sure you save the object into the store before getting the object ID. Until saved, they're temporary, and you can't access them from another thread.
  2. And beware of the merge policies if you make changes to the managed objects from more than one thread.
  3. NSManagedObjectContext's -mergeChangesFromContextDidSaveNotification: is helpful.

But let me repeat, please read the document carefully! It's really worth it!

Solution 2

Currently [May 2015] the Apple Concurrency with Core Data documentation is, at best, very misleading as it doesn't cover any of the enhancements in iOS 5 and hence no longer shows the best ways to use core data concurrently. There are two very important changes in iOS 5 - parent contexts and new concurrency/threading types.

I have not yet found any written documentation that comprehensively covers these new features, but the WWDC 2012 video "Session 214 - Core Data Best Practices" does explain it all very well.

Magical Record uses these new features and may be worth a look.

The real basics are still the same - you can still only use managed objects the thread their managed object context was created on.

You can now use [moc performBlock:] to run code on the right thread.

There's no need to use mergeChangesFromContextDidSaveNotification: anymore; instead create a child context to make the changes, then save the child context. Saving the child context will automatically push the changes up into the parent context, and to save the changes to disk just perform a save on the parent context in it's thread.

For this to work you must create the parent context with a concurrent type, eg:

mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

Then on the background thread:

context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[context setParentContext:mainManagedObjectContext];

<... perform actions on context ...>

NSError *error;
if (![context save:&error])
{
    <... handle error ...>
}
[mainManagedObjectContext performBlock:^{
    NSError *e = nil;
    if (![mainContext save:&e])
    {
        <... handle error ...>
    }
}];

Solution 3

I hope this can help all the peoples that meet problems using core data in a multithread environment.

Take a look at "Top Songs 2" in apple documentation. With this code i took the "red pill" of Matrix, and discovered a new world, without double free error, and without faults. :D

Hope this helps.

Paolo

p.s. Many thanks to Yuji, in the documentation you described above I found that example.

Share:
42,903
SlowTree
Author by

SlowTree

Mobile application developer iOS, Android, Symbian.

Updated on August 17, 2020

Comments

  • SlowTree
    SlowTree over 3 years

    I'm trying to use core data in a multi thread way. I simply want to show the application with the previously downloaded data while downloading new data in background. This should let the user access the application during update process.

    I have a NSURLConnection which download the file asyncronously using delegate (and showing the progress), then i use an XMLParser to parse the new data and create new NSManagedObjects in a separate context, with its own persistentStore and using a separate thread.

    The problem is that creating new objects in the same context of the old one while showing it can throws BAD_INSTRUCTION exception. So, I decided to use a separate context for the new data, but I can't figure out a way to move all the objects to the other context once finished.

    Paolo aka SlowTree

  • SlowTree
    SlowTree over 14 years
    I found a great example of merging contexts in CoreDataBooks (mergeChangesFromContextDidSaveNotification). Thank you very much. Have a nice day. Paolo aka SlowTree
  • tobyc
    tobyc almost 14 years
    Oh thank god. I reading this has solved my issues. Was importing a large set of data in a background thread and getting a multitude of unpredictable exceptions. Instantiating the context in the background thread rather than passing it though seems to have fixed my isses.
  • JosephH
    JosephH almost 12 years
    This document has not yet been updated to take advantage of very important improvements in iOS 5 - the video I link to in my answer is now a better reference.
  • João Nunes
    João Nunes over 11 years
    regarding rule 3. you can pass the object through threads and you can read it but you CANT change it!
  • Philip007
    Philip007 over 11 years
    Upvote for up-to-date info. SO should implement mechanism to promote answers involved with new tech development, and downplay those outdated accepted answers. Thanks for recommending Magic Record framework. Its documentation looks really nice. I might have a try later.
  • Philip007
    Philip007 over 11 years
    Contrary to your code snippet, UIManagedDocument by default create its parent context in private queue, and child context in main queue. Any idea why Apple does so? Is it totally arbitrary?
  • JosephH
    JosephH over 11 years
    Thanks, have corrected the WWDC link. I've not used UIManagedDocument, but having the top most context on a private thread is a common pattern, so that the save operation doesn't block the ui - if you have a lot of data to update, the operation can take a while.
  • JosephH
    JosephH over 11 years
    @JoãoNunes Unfortunately you can't reliably read objects on another thread - it might appear to work sometimes, but if you cause a faulted object to be loaded you're invoking (at best) undefined behaviour.
  • Will Larche
    Will Larche almost 11 years
    I get Can only use -performBlock: on an NSManagedObjectContext that was created with a queue Shouldn't the initWithConcurrancyType be privateQueue?
  • JosephH
    JosephH almost 11 years
    @WillLarche If you're managing the thread yourself, NSConfinementConcurrencyType is correct. If you want the managed object context to create/manage the thread for you and you want to use performBlock to pass work to that thread, NSPrivateQueueConcurrencyType would be correct. Both are definitely valid ways to work, it depends on the structure of your application which you would use.
  • Will Larche
    Will Larche almost 11 years
    @JosephH When I tried with Confinement I got an error saying I couldn't performBlock:.
  • JosephH
    JosephH almost 11 years
    @WillLarche You can use performBlock onto the main thread (as per the code in the answer), but you're right, you can't use performBlock on a managed object context created with NSConfinementConcurrencyType - in this case you're managing the thread yourself, you need to use the usual mechanism to run operations on that thread (eg. dispatch_async, queuing an NSOperation, etc).
  • quellish
    quellish almost 10 years
    If you want to see the concurrency guide updated, duplicate these radar bugs: openradar.me/radar?id=3166402 and openradar.me/radar?id=3166401
  • quellish
    quellish over 9 years
    Unfortunately the Core Data Programming guide section on Concurrency with Core Data is severely out of date. It describes thread confinement, which is obsolete (and has been for several years).
  • Bhumit Mehta
    Bhumit Mehta over 8 years
    The doccument no longer exists, can you update the link?
  • paulvs
    paulvs about 7 years
  • Awais Fayyaz
    Awais Fayyaz over 2 years
    you should share link regarding "Top Songs 2"