3

I have one doubt about multithreading in CoreData. If we are using multithreading we should use separate NSManagedObjectContext for inserting new data or updating or else we can use the parent-child context method. But I am creating new NSManagedObjectContext only. My question is - should I use separate NSManagedObjectContext for fetching even in a background thread as well? If not (i.e. we can use the main queue NSManagedObjectContext only) then why I am getting __psynch_mutexwait error?

Thanks

Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
Sachin
  • 333
  • 1
  • 4
  • 19

1 Answers1

18

First, Core Data is thread safe. However you must follow the rules:

  1. NSManagedObjectContext is thread bound. You can only use it on the thread it is assigned to. -init causes a context to be assigned to the thread it is created on. Using -initWithConcurrencyType: will allow you to create contexts associated to other threads/queues.
  2. Any NSManagedObject associated with a NSManagedObjectContext is tied to the same thread/queue as the context it came from
  3. There is no third rule

You can pass NSManagedObjectID instances between threads but rules 1 and 2 must be obeyed. From your description I believe you are violating these rules.

Personally I do not recommend using NSManagedObjectID either. There are better solutions. – Marcus S. Zarra

Marcus, this is the most succinct explanation of Core Data's threading I've read. Having used it since it's introduction, there are days I still get these rules wrong! You mention "better solutions" — can you elaborate?

I have a fairly strong distrust of the use of the NSManagedObjectID. It does not stay the same from one application lifecycle to another in many situations. Originally, based on the documentation, we (Cocoa developers in general) believed it was our mythical primary key being generated for us. That has turned out to be incorrect.

In modern development with parent/child contexts the landscape is even more confusing and there are some interesting traps that we need to watch out for. Given the current landscape I dislike it more than I did previously. So what do we use?

We should generate our own. It doesn't need to be much. If your data does not have a primary key from the server already (pretty common to have an id from a Ruby based server) then create one. I like to call it guid and then have an -awakeFromInsert similar to this:

- (void)awakeFromInsert
{
  [super awakeFromInsert];
  if (![self primitiveValueForKey:@"guid"]) {
    [self setPrimitiveValue:[[NSProcessInfo processInfo] globallyUniqueString] forKey:@"guid"];
  }
}

NOTE: This code is written in the web browser and may not compile.

You check the value because -awakeFromInsert is called once per context. Then I will generally have a convenience method on my NSManagedObject instances similar to:

@implementation MyManagedObject

+ (MyManagedObject*)managedObjectForGUID:(NSString*)guid inManagedObjectContext:(NSManagedObjectContext*)context withError:(NSError**)error
{
  NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:[self entityName]];
  [fetch setPredicate:[NSPredicate predicateWithFormat:@"guid == %@", guid]];

  NSArray *results = [context executeFetchRequest:request error:error];
  if (!results) return nil;

  return [results lastObject];
}

@end

NOTE: This code is written in the web browser and may not compile.

This leaves error handling and context/threading control up to the developer but provides a convenience method to retrieve an object on the current context and lets us "bounce" an object from one context to another.

This is slower than -objectWithID: and should be used carefully and only in situations where you need to bounce an object from one context to another after a save moves it up the stack.

As with most things I do; this is not a generic solution. It is a baseline that should get adjusted on a per project basis.

Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182
  • Is it important to note that NSManagedObjectID has to be permanent before is thread-safe (if that's correct)? – 3lvis Feb 22 '14 at 09:26
  • 1
    No, this is incorrect. The `NSManagedObjectID` is a mutable object and Core Data will mutate it when the save occurs and the ID moves from temporary to permanent. Personally I do not recommend using `NSManagedObjectID` either. There are better solutions. – Marcus S. Zarra Feb 24 '14 at 15:43
  • 2
    Marcus, this is the most succinct explanation of Core Data's threading I've read. Having used it since it's introduction, there are days I still get these rules wrong! You mention "better solutions" — can you elaborate? – Tony Arnold Mar 06 '14 at 23:10
  • You say there is no 3rd rule, yet you are allowed to use an `NSManagedObjectContext` from multiple threads if you `lock` and `unlock` it before every use (including implicit uses by `NSManagedObject`s). – Heath Borders Mar 08 '14 at 05:30
  • @HeathBorders that is incorrect. You can "get away" with accessing a `NSManagedObjectContext` from multiple threads with locks but it risks corruption and crashes. It violates the thread containment rules of Core Data and should **NOT** be done under any circumstances. – Marcus S. Zarra Mar 08 '14 at 08:02
  • @MarcusS.Zarra not with any lock, but with -[NSManagedObjectContext lock], though the documentation implies that a regular lock will work as well. "It is preferable to use the NSManagedObjectContext’s implementation of NSLocking instead using of a separate mutex object." – Heath Borders Mar 10 '14 at 04:13
  • @HeathBorders You are misinterpreting the documentation. – Marcus S. Zarra Mar 10 '14 at 05:01
  • @MarcusS.Zarra I've opened a new question about this http://stackoverflow.com/questions/22305613/when-is-it-safe-to-use-nsmanagedobjectcontext-lock – Heath Borders Mar 10 '14 at 16:20