read

As many of you may know, when I first started writing iOS apps for clients, I authored MagicalRecord. This library came directly from my work in discovering how to properly use Core Data. That is, once I figured out all the subtleties of working with various aspects of Core Data, I decided that I didn't want to write that code again, so I made myself a personal library to make sure I don't make the same mistakes again. MagicalRecord has reduced the boilerplate code required not only for fetching, but for threading as well.

One of the most common mistakes when using Core Data is crossing threads. Threading in general is not an easy thing to handle in any app, but when used in conjunction with Core Data, and the potential for crashes increases. That isn't to say Core Data doesn't work with threads. It just means you need to be very careful.

I originally wrote MagicalRecord before Grand Central Dispatch (GCD) was released, so we only had the classic threading model and NSThread APIs to work with. It was back then that contextForCurrentThread was submitted as an addition to MagicalRecord. At the time, it was a great addition. However, now, as more of you (and the heart of MagicalRecord) is using GCD, contextForCurrentThread is no longer safe for use in your apps.

The way contextForCurrentThread works is basically creating a single NSManagedObjectContext for a single thread, and storing that in the thread's dictionary. The thread dictionary is basically a way to hold on to arbitrary values across the life of the thread. In theory, this is great because you don't have to recreate a context all the time, or any items fetched in that context are already present in that thread. This isn't always necessary, but it's great if you need it. However, it's this long life of the context for a given thread is what can cause crashes for apps at seemingly random times.

GCD is a terrific API that takes care of managing and reusing threads for you. Your primary interface into GCD is via queues. And to do work on a queue, you simply schedule a block to it, and eventually, the queue will execute the block. The block, while running on a single queue, can potentially run on any thread managed by GCD. This is where problems start to arise. Say you have a context created via the contextForCurrentThread method, and then hold on to a reference to that context. You then submit more blocks onto the queue, and use the same context. There is an eventual possibility that one or more of the newly submitted blocks will be run on a thread that is not the thread in which the context was created. And, while this alone doesn't mean sudden death for your app, there will eventually be a crash because the context was access on the wrong thread, even though the queue is the same.

The way to handle this in the world of GCD is simple. Every time you perform some Core Data work in a block which will be run on a GCD queue is to create a new context for that block. Yes, you need to create a new context; some of you might think this is wasteful, but NSManagedObjectContexts are rather light weight (as defined by Apple). I'd prefer to take a slightly slower app that's stable over a lightning fast app that crashes unpredictably. MagicalRecord provides a simple API for this case, use the [-MagicalRecord saveWithBlock:] and related methods. These APIs will create a new block with a fresh new NSManagedObjectContext for you, set it up to merge so that your data is available in the rest of your app, and save the context for you once your data manipulations are complete. Of course, you should follow the code examples so that your objects are properly transferred to each of these contexts. This works simply because of the nature of GCD queues, since a block can run on any thread, and once a block is running, it's (essentially) a serial operation on that thread. Each block has it's own NSManagedObjectContext and thus all data operations will only be accessed on the same thread. Yes, you're making far more contexts, and doing a bit of work in making sure you're using objectIDs to transfer data across contexts, but this is how Core Data works best with threads.

I hope this explains a bit why contextForCurrentThread is (now) a dangerous pattern, and will be removed from MagicalRecord in the near future. I urge you all to use -[MagicalRecord saveWithBlock:] and other methods provided by MagicalRecord to handle your Core Data threading needs.

Blog Logo

Saul Mora


Published

Image

Saul Mora

iOS Developer, Engineer, Dad, Human

Back to Overview