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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With