Note that there are some explanatory texts on larger screens.

plurals
  1. POGeneric approach to NSManagedObjectContext in multi-threaded application
    text
    copied!<p>I've read a number of posts here about NSManagedObjectContext and multi-threaded applications. I've also gone over the CoreDataBooks example to understand how separate threads require their own NSManagedObjectContext, and how a save operation gets merged with the main NSManagedObjectContext. I found the example to be good, but also too application specific. I'm trying to generalize this, and wonder if my approach is sound.</p> <p>My approach is to have a generic function for fetching the NSManagedObjectContext for the current thread. The function returns the NSManagedObjectContext for the main thread, but will create a new one (or fetch it from a cache) if called from within a different thread. That goes as follows:</p> <pre><code>+(NSManagedObjectContext *)managedObjectContext { MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; NSManagedObjectContext *moc = delegate.managedObjectContext; NSThread *thread = [NSThread currentThread]; if ([thread isMainThread]) { return moc; } // a key to cache the context for the given thread NSString *threadKey = [NSString stringWithFormat:@"%p", thread]; // delegate.managedObjectContexts is a mutable dictionary in the app delegate NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts; if ( [managedObjectContexts objectForKey:threadKey] == nil ) { // create a context for this thread NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] autorelease]; [threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]]; // cache the context for this thread [managedObjectContexts setObject:threadContext forKey:threadKey]; } return [managedObjectContexts objectForKey:threadKey]; } </code></pre> <p>Save operations are simple if called from the main thread. Save operations called from other threads require merging within the main thread. For that I have a generic <code>commit</code> function:</p> <pre><code>+(void)commit { // get the moc for this thread NSManagedObjectContext *moc = [self managedObjectContext]; NSThread *thread = [NSThread currentThread]; if ([thread isMainThread] == NO) { // only observe notifications other than the main thread [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:moc]; } NSError *error; if (![moc save:&amp;error]) { // fail } if ([thread isMainThread] == NO) { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:moc]; } } </code></pre> <p>In the <code>contextDidSave:</code> function we perform the merge, if called by the notification in <code>commit</code>.</p> <pre><code>+(void)contextDidSave:(NSNotification*)saveNotification { MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; NSManagedObjectContext *moc = delegate.managedObjectContext; [moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:saveNotification waitUntilDone:YES]; } </code></pre> <p>Finally, we clean-up the cache of NSManagedObjectContext with this:</p> <pre><code>+(void)initialize { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadExit) name:NSThreadWillExitNotification object:nil]; } +(void)threadExit { MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]]; NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts; [managedObjectContexts removeObjectForKey:threadKey]; } </code></pre> <p>This compiles and seems to work, but I know threading problems can be tricky due to race conditions. Does anybody see a problem with this approach?</p> <p>Also, I'm using this from within the context of an asynchronous request (using ASIHTTPRequest), which fetches some data from a server and updates and inserts the store on the iPhone. It seems NSThreadWillExitNotification doesn't fire after the request completes, and the same thread is then used for subsequent requests. This means the same NSManagedObjectContext is used for separate requests on the same thread. Is this a problem?</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload