Thoughts on Core Data Stack Configurations

Michael Gachet
BPXL Craft
Published in
6 min readNov 9, 2015

This post relies on some prior knowledge of Core Data and the persistence stack. It is based on personal experience with Core Data. If you are new to Core Data, you may want to have a look at my previous article about lessons learned on Core Data first.

A Pretty Good Start

There is a Core Data stack configuration that is a very good start and will cover most use cases. It uses two contexts by default:

  1. A PrivateQueue context, which is connected to the persistence store.
  2. A MainQueue context, which is a child of the PrivateQueue context and is used to perform all UI operations.
A typical Core Data Stack basic configuration.

The advantage of this configuration is that the UI context saves all its objects in memory: no latency for saves. And the persistence to disk can be executed asynchronously on the PrivateQueue context: saving does not block the UI.

This setup is typically abstracted away by wrapping the setup in a persistence controller that you pass around to your view controllers. For a good starting point, have a look at the post by Marcus Zarra on that topic.

If you work with a networking application you will need to determine which context should be used to import changes from your server. There are two configurations that are very often used.

The “Background Context as a Sibling” Configuration

In this configuration you perform background operations on a context that is a sibling to the main UI context.

“Siblings” configuration.

With this configuration everything you do in the background context is invisible to the main UI context by default and vice versa.

The most practical way to propagate changes from the background context to the main context in this case is to have the main UI context register as an observer of the NSManagedObjectContextDidSaveNotification notification.

Upon receiving this notification you would merge the changes by using a code similar to this, typically in your persistence controller but not necessarily:

Note that if your application uses different Core Data stacks (maybe a third-party analytics package uses one), you will need to be extra cautious about this approach and may have to rely on a different mechanism. For instance, using a custom notification that triggers fetch on your main context to refresh its content.

The biggest advantage of this stack configuration is that the main thread is isolated from changes made to the background thread until you merge them into the main thread.

The biggest inconvenience is the merge procedure. As with every merge, there may be conflicts that will need to be resolved by specifying the relevant merge policy on your main context. Which merge policy to use really depends on the application. For example, if the main context is considered “read-only” then MergeByPropertyStoreTrumpMergePolicyType on the main context and MergeByPropertyObjectTrumpMergePolicyType on an import context could be used.

The “Background Context as a Child” Configuration

In this configuration you perform all background operations on a child of the main UI context.

Left: “Sibling” Stack, Right: “Children” Stack.

The main advantage of this configuration is that all changes made to the background context are automatically saved into the main UI context. There is no need to merge changes as this is done automatically for you. However, this approach is not fail safe and you may still encounter save issues.

One main drawback of this configuration is the performance impact. All stack operations made from the background context (fetch/add/delete) will transit through the main UI context. If you have many such requests it may significantly impact the work performed by the main thread to process these requests.

Florian Kugler wrote an interesting post awhile back on the topic of core data stack performance if you want to know more.

On Background Contexts

Consider the following realistic scenario where two network operations are added to a concurrent queue:

  • Operation A operates on context A to retrieve objects of type A from the backend.
  • Operation B operates on context B to retrieve objects of type B from the backend.

What will happen if there is a relationship between objects of type A and objects of type B? What will happen when objects in context A are deleted while still regarded as valid in context B?

Having both operations using two different contexts will most likely lead to crashes that will be very difficult to reproduce, identify, isolate, and fix.

In such a case it would be a lot safer and easier to use a single background context to perform both networking operations A and B.

The Main UI Context

Another thing worth thinking about is what should happen to operations using background contexts when a critically important object is deleted by the user in the main UI context?

Consider the following setup:

  • The managed object User can have many managed Post objects. The relationship is one to many.
  • After launch, the application starts updating the posts in the background.
  • The user logs out or deletes his account while the update is taking place.

A “naive” way to go about this could be to write something like this:

I’ve taken this approach many times myself. It makes perfect sense to delete an object directly when you don’t need it anymore. But doing so exposes you to concurrency crashes that are very hard to reproduce.

A better, albeit imperfect, approach is to mark objects as “deleted” instead of deleting them and defer the actual deletion until later.

This code is almost similar to the “naive” one … with the exception that when the user logs out or deletes her account, the corresponding object is merely marked as “deleted.” Proceeding this way is a lot safer than directly deleting objects from the object graph when your application contains several managed object contexts.

Contexts and Stale Data

When you deal with parent-child contexts, always remember that changes to the parent are not pushed down to its children. This means that the information in the child context may be stale. They may still be holding references to objects that are no longer valid. Be aware of this and devise the proper strategy to deal with it in your application.

One such strategy could be to keep the background context around for as long as the main UI context exists. In that case it is safest to assume that your objects are stale before making any operation and either:

  • Call reset() on your context to clear out any stale objects and reduce memory usage.
  • Fetch the objects you need to work with.
  • Refresh your context using the refreshObject(: , mergeChanges:) method on NSManagedObjectContext.

Another strategy is to create a background context on demand, and delete it once it has performed its duty. For instance, you could create a background context at the start of your update sequence and discard it once all updates have completed. Just be aware of the risk inherent to creating many sibling background contexts.

There Is so Much More

Core Data and concurrency is a vast topic. There can be many different stack configurations (as we were editing this post Big Nerd Ranch introduced their Core Data Stack).

No matter what your stack is you need to really understand it and pay close attention whenever you are dealing with either sibling or parent-child contexts. Neither configuration is foolproof nor fail safe.

We hope this article shed some light on some very practical and common stacks and the things to keep in mind when dealing with them. If you want to learn even more about this topic, I recommend a book (now in early access) by Florian Kugler and Daniel Eggert from objc.io.

Good luck with your next Core Data project.

--

--

Michael Gachet
BPXL Craft

Curious mind, iOS developer, runner. All views are mine...