Question

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

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top