Easier Core Data Setup with Persistent Containers

Apple has made a number of changes in iOS 10 to ease the pain of setting up and using Core Data. In this post I look at how to use the new persistent container class to remove a lot of the boilerplate Core Data setup code. If you are still relying on the old Xcode template code for Core Data in your app delegate you should take a look at NSPersistentContainer.

Core Data Stack Setup - The Easy Way

Setting up a Core Data stack takes some work. A typical setup needs a number of steps:

This is a lot of work and results in a page of setup code before you can do anything with the managed objects. Starting with iOS 10 the NSPersistentContainer removes most of this boilerplate code.

If you adopt some simple naming conventions and the default store location the setup of a core data stack can be as simple as this:

container = NSPersistentContainer(name: "storeName")
container.loadPersistentStores { (storeDescription, error) in
  if let error = error {
    fatalError("Failed to load store: \(error)")
  }
}

The Objective-C version:

container = [NSPersistentContainer persistentContainerWithName:@"storeName"];
[container loadPersistentStoresWithCompletionHandler:
 ^(NSPersistentStoreDescription *storeDescription, NSError *error) {
  if (error != nil) {
    NSLog(@"Failed to load store: %@", error);
    abort();
  }
}];

The persistent container uses the name you pass for both the managed object model (name.momd) and the name of the persistent store (name.sqlite) which it creates or loads from a default directory (Application Support).

You can also load the managed object model yourself before creating the persistent container:

guard let mom = NSManagedObjectModel.mergedModel(from: nil) else {
  fatalError("Could not load model")  
}

container = NSPersistentContainer(name: name, managedObjectModel: mom)

Persistent Store Descriptor

A persistent container collects the settings for a persistent store into a persistent store descriptor. To override the defaults create a new descriptor before loading the store. You create a persistent store descriptor with the URL for the store. You can get the default store location from the container class:

let defaultURL = NSPersistentContainer.defaultDirectoryURL()
let description = NSPersistentStoreDescription(url: defaultURL)

The default URL puts the store in the Application Support directory of the application container.

The most commonly used configuration settings:

So to create a read-only persistent store that loads asynchronously on a background thread (and by default that migrates automatically):

let url = NSPersistentContainer.defaultDirectoryURL()
let description = NSPersistentStoreDescription(url: url)
description.shouldAddStoreAsynchronously = true
description.isReadOnly = true
container.persistentStoreDescriptions = [description]

Note that you need to set the store description before you load the store.

Getting the Main View Context

The persistent container has a convenient read-only property named viewContext to get the managed object context for the main queue. As the name suggests this is the context you should be using when working with your user interface.

container.viewContext.perform({
  // ...
})

I should note at this point that Apple tells us that this view context automatically consumes save notifications from other notifications. This removes some more boilerplate code to listen for and merge save notifications but I have found in practise that I need to set the flag automaticallyMergesChangesFromParent on the view context for it to work:

// let viewContext = container.viewContext
viewContext.automaticallyMergesChangesFromParent = true

Not sure if that is a bug or a feature.

Performing a Background Task

To avoid blocking the user interface you should not use the main view context for time consuming tasks. Create a private managed object context and execute the task in the background. The persistent container has a convenience method that takes care of creating a temporary private context for you and takes a block to execute:

container.performBackgroundTask({ (context) in 
  // ... do some task on the context

  // save the context
  do {
    try context.save()
  } catch {
    // handle error
  }
})

Getting a Private Context

You can also just get a new private context to use anyway you see fit:

let context = persistentContainer.newBackgroundContext()
context.perform({
  // ...
})

Wrapping It All

I have found it useful to wrap the persistent container in a class to hide the details of the persistent store descriptor setup for the common use cases (CoreDataController Swift and Objective-C). I default to loading the store asynchronously and automatically merging changes. A typical setup of a Core Data stack ends up something like this:

// var dataController: CoreDataController?
dataController = CoreDataController(name: storeName)
guard let dataController = dataController else {
  fatalError("Unable to build Core Data stack")
}

dataController.loadStore { (error) in
  if error != nil {
    // Handle error ...
  }
}

viewController.managedObjectContext = dataController.viewContext

Much nicer, assuming you can drop support for iOS 9…

Further Reading

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus