Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Help with multithreaded Core Data app design

alt text

Above is a simplification of what my model looks like. My app has a NSWindowController object controlling two NSViewController objects for the user and account entities. When a user logs in to the app, they can modify user or account information by bringing up the relevant view controller. In the background I have the application periodically populating the user's logs in the application delegate on a separate thread.

I am using a separate NSManagedObjectContext for the background thread and the application delegate's NSManagedObjectContext for data entry in the view controllers. I would like to know a few things:

1) is this good practice? Should I create a NSManagedObjectContext for each view controller and then merge the contexts whenever the user is done making changes?

2) Because the log entity is created in the background thread, it has it's own NSManagedObjectContext. However, each log includes information from the user and account entities, which are created in the application delegate's NSManagedObjectContext. This is how I am fetching a user:

- (NSManagedObjectID*) fetchUser:(NSString*) userID {   
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];   
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"user":inManagedObjectContext:self.managedObjectContext];
    /** snip **/
}

This method is called by the background thread as follows:

NSManagedObjectID* userObjectID = [self fetchUser:userID];
NSManagedObject* userObject = [self.logsManagedObjectContext objectWithID:userObjectID];

Is what I'm doing in fetchUser thread-safe? Do I need to lock the main managed object context while fetching a user in case one of the views is modifying the same user? From this article I understand (perhaps incorrectly) that I may have to do so. So far I haven't run into any problems but I don't want to leave a potential edge case.

3) When one of the view controllers makes changes to the application delegate's NSManagedObjectContext it posts a notification that is handled as follows:

- (void)contextDidSave:(NSNotification *)notification {
    SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
    [self.logManagedObectContext performSelector:selector onThread:backgroundThread withObject:notification waitUntilDone:NO];
}

Is this how I should handle the merge or should I be merging the application delegate's NSManagedObjectContext instead? I found that doing that (on the main thread) locked up the UI.

Any help will be appreciated.

like image 708
David Avatar asked Dec 17 '10 09:12

David


People also ask

How we can do multithreading with Core Data?

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.

Is Core Data thread safe a Nsmanagedobjectid?

Core Data Concurrency The NSManagedObjectContext perform(_:) and performAndWait(_:) functions are thread safe and can be called from other threads. A NSManagedObject cannot be shared across threads.

Is Managedobjectcontext thread safe?

Managed Object ContextThe NSManagedObjectContext class isn't thread safe. Plain and simple. You should never share managed object contexts between threads. This is a hard rule you shouldn't break.


1 Answers

NSManagedObjectContext objects are not thread-safe. This means that if you wish to access Core Data from multiple threads, you will need one for each thread (and created on the thread too). Each of these can use the same NSPersistentStoreCoordinator, which will serialise access to the persistent store.

This occurs because each NSManagedObjectContext knows how to properly lock the NSPersistentStoreCoordinator when it is in use, avoiding collisions. By following these rules, you should remain thread-safe.

As you're already doing, NSManagedObjectID objects should be used to pass Core Data objects from one MOC to another (and by extension from one thread to another). However you are calling fetchUser: which uses the MOC from your main thread, on a background one. This isn't correct. That fetchUser: method call must be called from the main thread. Of course, there's nothing to stop you from retrieving the user in the background thread using the background MOC.

In summary, always make calls to an NSManagedObjectContext from the thread it was created in.

The trick here is to make sure that both MOCs know about the other's saves, so you must register to receive the notifications from each context. You should then be performing the mergeChangesFromContextDidSaveNotification: from the appropriate thread for the MOC. At the moment, your background context is being notified about changes from the main thread's context, but not vice versa.

Oh, and there's no need to have a separate context for each NSViewController. As UI elements, their interactions with the context will occur on the same (main) thread, so sharing is fine.

like image 191
paulbailey Avatar answered Sep 20 '22 20:09

paulbailey