4
May
2011
 

Core Data and Threads, Without the Headache

by Saul Mora

I know I mentioned we would talk about customizing the fetch requests, however, I have been working on some code related to the Active Record Fetching project, which I am renaming to MagicalRecord, that is also just as useful as fetching–threading.

Whenever most cocoa developers mention threading and Core Data in the same sentence, the reaction I see most often is that of mysticism and disbelief. For one, multithreaded programming in general is hard–hard to design correctly, hard to write correctly, and debugging threads is just asking for trouble. Introducing Core Data into that mix can seem like the straw that broke the camel’s back. However, by following a few simple rules and guidelines, and codifying them into a super simple pattern, one that may be familiar to you, we can achieve safer Core Data threading without the common headaches.

Another reminder: the examples in this blog post will refer to the open source project on Github, MagicalRecord, which is basically several helpful categories and classes I’ve written to extend the standard Core Data APIs.

What would Apple do?

Let’s start by looking up in the docs (see Concurrency with Core Data) to see what needs to happen in order to set up Core Data to pass data objects across threads and perform save operations in the background. Summarizing the main points from the recommended approach:

  • Each thread should have its one NSManagedObjectContext
  • NSManagedObjects themselves are not thread-safe
  • NSManagedObjectIDs are thread-safe
  • If you save in the background, you need to merge your changes to other contexts using one of the Core Data system notifications, NSManagedObjectContextDidSaveNotification.

How do we do this in code? The most common solution is to create a custom NSOperation, and make sure we create and set up our new context in the main method of the operation, like so:

#import "MGPCoreDataOperation.h"

@interface MGPCoreDataOperation ()

@property (nonatomic, retain) NSManagedObjectContext *context;

@end

@implementation MGPCoreDataOperation

- (void)main
{
	[self setContext:[NSManagedObjectContext context]];

	//perform my core data operations here
}

@end

Remember to create the NSManagedObjectContext in the main method of your custom NSOperation, and not the init method. The main is the only one of the two that is guaranteed to be called from another thread. Your secondary NSManagedObjectContext needs to be created inside the thread where it will be used.

This solution is fairly straightforward, however, let’s think beyond this class. Using this approach, while a valid solution, is somewhat cumbersome to reuse. For every save operation using this technique you will:

  • need to subclass, or create this class via a template every time
  • instantiate this objects on the main thread, and give it to an NSOperationQueue
  • have to pass in object ids through a property

This solution is for the heavy lifting algorithms such as parsing data in the background and saving it in once the parse is done, and all on a background thread. Let’s try a solution that is a little lighter weight, a little easier on the eyes, and uses one of the more fun and recent additions to Objective C–blocks and GCD.

A small idea

Before we get started with our threading adventure, we need to first seed our minds with a small idea that will help us for the remainder of this post. In the context of threading and UI, we need to examine the concept of a Main Thread. The main thread is different from other threads in that it is considered the foreground thread, and is the thread which runs the main runloop, which in turn, processes messages sent from outside your app such as taps or clicks, and sends them to your application. From this example, let’s introduce the concept of a Main Managed Object Context. This context is meant to only be accessed from the same thread as the UI. This context is also always available, as is the Main Thread, and Main Runloop. In the MagicalRecord helpers, I have implemented a defaultContext class method that provides access from anywhere.

The default Xcode Core Data application template promotes this idea by virtue of putting a reused managed object context in the app delegate. Both the app delegate, and the managed object context are implied singletons. MagicalRecord also keeps it’s default context in a single place for easy reuse in your applications, and is also a singleton-like pattern. But, unlike the application templates, it keeps its reference away from your app delegate. Technically speaking, moving the shared NSManagedObjectContext out of the app delegate is merely a preference, but my preference is to keep the application delegate as free from actual application responsibility as possible.

This main context provides some neat features. First, it let’s us simplify the find methods described in my previous post, while still having the option for a context parameter. Second, this lets us get started with Core Data very quickly since we can do fetching from the main thread on the main context, and not have cross thread issues. And third, and most importantly, we have a single context to which any background changes are merged. And if those benefits weren’t helpful enough, changes to observed managed object instance can immediately be acted upon by any UI components using KVO.

Core Data, Blocks and GCD…oh my!

Multithreading in Core Data is important when it comes to saving data in the store. While complex fetches can certainly take quite a bit of time to return, most frequently the bottleneck is during a save. As such, the goal in this solution is to make background saving easy and safe.

This block based solution is inspired by one of the newer iOS APIs for animating views. Blocks are incredibly powerful, and for Core Animation code, have greatly simplified animation code. A simple animation now looks like this:

UIView *redView = ...;
[UIView animateWithDuration:5.0
		 animations:^{
			redView.alpha = 0.0;
		 }
 ];

Can we use this paradigm to save Core Data objects on a background thread? What if we had a save API resembling this animation API? Let’s first think about the synchronous (aka. easy) case. The API would probably look something like this:

+ (void) saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock;

When using this API our code will resemble this sample:

PersonEntity *person = ...;
NSManagedObjectID *objectID = [person objectID];
[NSManagedObjectHelper saveDataInContext:^(NSManagedObjectContext *localContext){
	PersonEntity *localPerson = (PersonEntity *)[localContext objectWithID:objectID];

	//make my updates to localPerson here
	localPerson.name = @"IronMan";
	//...more changes
}];

While it’s not terribly important in this particular example because it’s all running in the same tread, I must remind you that NSManagedObjects are NOT thread-safe, but NSMangedObjectIDs are, so we need to pass an NSManagedObject’s object ID across the thread, or in this case, block boundary.

In order for this code sample to work, the implementation of the saveDataInContext method should be:

  • creating a new NSManagedObjectContext instance, AND
  • setting up the both contexts with the proper merge policy, AND
  • setting up the main context to listen to the background context to merge on save,
  • sending that context as the parameter into the block for use by other code.

This would make creating lightweight save or update operations as easy as creating a UIView block-based animation.

So, what would the body of this method look like? Let’s start the first item on the list.

+ (void)saveDataInContext:(void(^)(NSManagedObjectContext *context))saveBlock
{
	NSManagedObjectContext *context = [NSManagedObjectContext context];			//step 1
	[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];				//step 2
	[defaultContext setMergePolicy:NSMergeObjectByPropertyStoreTrumpMergePolicy];
	[defaultContext observeContext:context];						//step 3

	block(context);										//step 4
	if ([context hasChanges])								//step 5
	{
		[context save];  //MagicalRecord will dump errors to the console with this save method
	}
}

This code uses some MagicalRecord helper methods, but the steps as implemented are:

  • Step 1: Create the new context
  • Step 2: Set the merge policy
  • Step 3: Set up the notification
  • Step 4: Call back our block with our newly created context that’s been set up properly.
  • Step 5: Save the context if it has any changes

Notice also, that in order for a merge to occur automatically, one context has to be set to win during the merge. In this case, we want the background context to win by telling the main context that we want the information in our data store to be the correct answer in the case of a conflict. Also notice that much of the setup of a second context is as simple as calling a single method. We can now build upon this simple foundation and introduce background threading for our saves. Since we’re using blocks, we can also take advantage of the Grand Central Dispatch APIs.

A background operation using our synchronous method can easily be written using GCD:

+ (void)saveDataInBackgroundWithContext:(void(^)(NSManagedObjectContext *context))saveBlock
{
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
		[self saveDataInContext:saveBlock];
	});
}

Going the Distance

But, can we do more? What is one neat feature of the UIView block animation APIs? Their ability to call a block when the animation has completed. How useful would it be to have a block of code called exactly after the changes to your object have been saved and merged? I thought so; let’s look again at a UIView animation API for inspiration:

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

And a usage sample, expanding on our previous UIView animation example:

UIView *redView = ...;
[UIView animateWithDuration:5.0
		 animations:^{
			redView.alpha = 0.0;
		 }
		 completion:^(BOOL  completed){
			[redView removeFromSuperview];
			[redView release];
			redView = nil;
		 }
];

To be safe, let’s make sure our completion block is called on the main thread while we’re at it.

+ (void)saveDataInBackgroundWithContext:(void(^)(NSManagedObjectContext *context))saveBlock completion:(void(^)(void))completion
{
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
		[self saveDataInContext:saveBlock];

		dispatch_sync(dispatch_get_main_queue(), ^{
			completion();
		});
	});
}

Now, you can save and reload, or update your data in a single place sort of like this:

 
NSArray *listOfPeople = ...;
[NSManagedObjectHelper saveDataInBackgroundWithContext:^(NSManagedObjectContext *localContext){
	for (NSDictionary *personInfo in listOfPeople)
	{
		PersonEntity *person = [PersonEntity createInContext:localContext];
		[person setValuesForKeysWithDictionary:personInfo];
	}
} completion:^{
	self.people = [PersonEntity findAll];
}];

This small block of code does a lot for you. It,

  • creates and sets up a background Managed Object Context
  • creates and sets up a background GCD queue
  • saves the background context, which was set up to notify our Managed Object Context in the Main thread
  • calls back your completion block on the main thread AFTER the changes have been pushed all the way to the persistent store

However, there is one more thing we could add to help keep our saving simple. As it is now, the background operations are performed on a global background queue. I’ve encountered times where it is necessary to have a single dedicated GCD queue only for Core Data operations. In MagicalRecord, we’ve defined a simple static function that allows access to a write only GCD queue.

static dispatch_queue_t coredata_background_save_queue;

dispatch_queue_t background_save_queue()
{
    if (coredata_background_save_queue == NULL)
    {
        coredata_background_save_queue = dispatch_queue_create("com.magicalpanda.coredata.backgroundsaves", 0);
    }
    return coredata_background_save_queue;
}

And this would only modify our background core data save method like so:

+ (void)saveDataInBackgroundWithContext:(void(^)(NSManagedObjectContext *context))saveBlock completion:(void(^)(void))completion
{
	dispatch_async(coredata_background_save_queue(), ^{
		[self saveDataInContext:saveBlock];

		dispatch_sync(dispatch_get_main_queue(), ^{
			completion();
		});
	});
}

Using blocks, GCD and a little bit of convention, we’ve helped to make multithreaded Core Data code much easier to write and stay consistent with Apple’s recommended methods. Not only that, but this solution consolodates much of the manual boilerplate setup required to properly save data in the background and safely update data throughout your applications.

Comments

zach.whelchel says:

Nice tutorial. I’ve implemented in into my code and am now stuck when it comes to syncing the two managedobjectcontexts. how can i make the local or new one simply replace the default context? if i merge them it just adds everything together. How would I go about doing this?

Saul Mora says:

So, to have your new background context not merge on save, you can unregister the observer. In MagicalRecord, I put in a helper method on nsmanagedobjectcontect called notifiesDefaultContext, or sOmething close to that. You’ll see that under the covers, it does something like

[[nsnotificationcenter defaultcenter] removerObserver:defaultContext name:nsmanagedobjectcontextdidsavenotification object:localContext]

You could also simply create your own context within that background block…