Building The Perfect Core Data Stack

Two Is Better Than One

Building The Perfect Core Data Stack
1 Bring Your Own
2 Two Is Better Than One
3 Keeping It Private Plus
4 Passing It Around Plus
5 Give It Time Plus

Have you ever considered using more than one managed object context in a Core Data application? In this installment of Building the Perfect Core Data Stack, we explore the options you have and the benefits of a Core Data stack with multiple managed object contexts.

Why Is Two Better Than One

While many applications can get away with a single managed object context, there will be times when one managed object context won't cut it. The setup I have come to appreciate looks like this.

Two Is Better Than One

The Core Data stack doesn't look overly complex. Right? Let me explain how it works and what the benefits are.

Even though the Core Data stack includes one persistent store coordinator and two managed object contexts, only one managed object context has a reference to the persistent store coordinator. This managed object context is private and only accessible by the class that manages the Core Data stack, the CoreDataManager class we created in the previous lesson of this course.

The second managed object context is a child of the private managed object context or, put differently, the private managed object context is the parent managed object context of the second managed object context. The child managed object context doesn't know about the persistent store coordinator.

Why is this necessary? What are the benefits of using two managed object contexts? The private managed object context operates on a background queue. This means that it does not block the main thread, the thread on which the user interface is updated, when it performs operations.

The second managed object context, which is a child of the private managed object context, operates on the main thread, which makes it ideal for any operations that involve the user interface. I talk more about threading and concurrency later in this course.

Using parent and child managed object contexts is also known as nesting managed object contexts. When using nested managed object contexts, there are a number of consequences you need to be aware of.

Saving Changes

When a managed object context saves its changes, it pushes those changes to the persistent store coordinator it is linked to and the persistent store coordinator pushes the changes to the persistent store(s) it manages.

But what happens if a managed object context is not linked to a persistent store coordinator? The main managed object context in the above diagram does not know about the persistent store coordinator of the Core Data stack. If a managed object context is a child of another managed object context, the changes are pushed to its parent managed object context when a save operation is performed. This implies that the changes are not written to disk when a save operation occurs.

This is good, though. Writing data to disk can take up a non-trivial amount of time. If this happens on the main thread, it is blocked as long as the write operation is ongoing. Remember that the user interface is also updated on the main thread and, therefore, a write operation could temporarily freeze the user interface, which is something we want to avoid at any cost.

By using a private managed object context that operates on a background thread, we can push changes to the persistent store coordinator without blocking the main thread. The changes that are pushed from the child managed object context to the private managed object context won't have a significant impact on the performance or responsiveness of the application because no data is written to disk.

While the Core Data stack is a bit more complex, the benefits far outweigh the slight increase in complexity. This is especially true if we take concurrency into account.

Updating the Core Data Stack

There are a few changes we need to make to the CoreDataManager class. Clone the project we created in the previous lesson from GitHub and open it in Xcode.

Adding & Updating Properties

In the CoreDataManager class, we change the name of the managedObjectContext property to privateManagedObjectContext. We also make the property private and initialize the managed object context by passing in .PrivateQueueConcurrencyType as the first argument. By passing in .PrivateQueueConcurrencyType as the first argument of the initializer, the managed object context is tied to a private dispatch queue, which is what we want.

private lazy var privateManagedObjectContext: NSManagedObjectContext = {
    // Initialize Managed Object Context
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)

    // Configure Managed Object Context
    managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator

    return managedObjectContext
}()

We also need to create a new property for the main managed object context, which will be tied to the main thread. This is the managed object context that we use for any operations related to the user interface and we also use this managed object context to create new NSManagedObjectContext instances, for example, for background operations.

public private(set) lazy var mainManagedObjectContext: NSManagedObjectContext = {
    // Initialize Managed Object Context
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)

    // Configure Managed Object Context
    managedObjectContext.parentContext = self.privateManagedObjectContext

    return managedObjectContext
}()

The implementation looks very similar. The main difference is that this managed object context is publicly accessible and has no reference to a persistent store coordinator. Note that the parentContext property of the main managed object context is set to the private managed object context we created a moment ago.

Update the implementation of application(_:didFinishLaunchingWithOptions:) as shown below and run the application in the simulator.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    print(coreDataManager.mainManagedObjectContext)
    return true
}

Saving Changes

To end this lesson, I would like to show you how to save changes when working with multiple managed object contexts. Because the main managed object context is a child of the private managed object context, invoking save() on the main managed object context only pushes the changes to the parent managed object context, not to the persistent store. While that is fine for most operations, you also need to push the changes to the persistent store at some point to make sure they are written to disk.

The saveChanges() method you see below is a public instance method of the CoreDataManager class that:

  • pushes the changes from the main managed object context to the private managed object context
  • pushes the changes of the private managed object context to the persistent store coordinator

There are several details worth pointing out.

// MARK: - Helper Methods

public func saveChanges() {
    mainManagedObjectContext.performBlockAndWait({
        do {
            if self.mainManagedObjectContext.hasChanges {
                try self.mainManagedObjectContext.save()
            }
        } catch {
            let saveError = error as NSError
            print("Unable to Save Changes of Main Managed Object Context")
            print("\(saveError), \(saveError.localizedDescription)")
        }
    })

    privateManagedObjectContext.performBlock({
        do {
            if self.privateManagedObjectContext.hasChanges {
                try self.privateManagedObjectContext.save()
            }
        } catch {
            let saveError = error as NSError
            print("Unable to Save Changes of Private Managed Object Context")
            print("\(saveError), \(saveError.localizedDescription)")
        }
    })
}

Notice that we first save the changes of the main managed object context. This is important because we need to make sure the private managed object context includes the changes of its child managed object context.

For this reason, we use performBlockAndWait(_:) instead of performBlock(_:). The latter asynchronously executes the block we hand it and that is not what we want. We first want to make sure the changes of the main managed object context are pushed to the private managed object context before pushing the changes of the private managed object context to the persistent store coordinator. To save the changes of the private managed object context, its is fine to use performBlock(_:).

Because the save() method is a throwing method, we wrap it in a do-catch block. Also note that we only invoke the save() method if the managed object context has any changes. We don't want to waste resources if there are no changes to save.

It is up to you when and how frequently you invoke the saveChanges() method. As an example, I have updated the AppDelegate class to invoke saveChanges() when the application is pushed to the background and when it is about to be terminated.

// MARK: - Application Life Cycle

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    print(coreDataManager.mainManagedObjectContext)
    return true
}

func applicationWillResignActive(application: UIApplication) {
    coreDataManager.saveChanges()
}

func applicationWillTerminate(application: UIApplication) {
    coreDataManager.saveChanges()
}

To further optimize the implementation, you could start a background task to make sure Core Data is given enough time to push the changes to the persistent store(s), because such an operation can take up a non-trival amount of time.

What's Next?

By making use of two managed object contexts, we have built a robust foundation which we can expand upon. In the next lesson, we take a closer look at concurrency, a key aspect of working with Core Data. Questions? Leave them in the comments below or reach out to me on Twitter. You can find the source files on GitHub.

Next Episode "Keeping It Private"

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By