Creating the Core Data Stack with Backwards Compatibility in Swift

In 2017, we live in a world where there are still non-iOS 10 devices out in the wild. If your app is targeting an iOS version earlier than iOS 10, or macOS Sierra (10.12), you’ll be unable to take advantage of Core Data’s latest “stack creation” class called NSPersistentContainer. So what can you do?

While NSPersistentContainer does aim to simplify the stack creation process, at the end of the day, it’s not terrible to have to mess with some of this Core Data plumbing.

End goal of creating the Core Data stack

The end goal of Creating the Core Data stack is to get an instance of NSManagedObjectContext. That’s it.

Most apps that rely on Core Data will end up making fetch requests to obtain data, or wire up other classes like NSFetchedResultsController. To make these things work, you’ve got to provide an instance of NSManagedObjectContext.

NSManagedObjectContext is sort of that central gear in the whole system that makes the other gears turn.

So. Bottom line: Once you have an instance of NSManagedObjectContext, you’re golden. That’s what creating the Core Data stack gives you in the end.

3 steps to creating the Core Data stack

The Core Data stack can be created in about 3 steps:

1) Initialize an instance of NSManagedObjectModel

This corresponds to your .xcdatamodeld file. You’ll want to glance over to the project navigator on the left and locate the .xcdatamodeld file to record its name for this step.

1
2
3
// Initialize NSManagedObjectModel
let modelURL = Bundle.main.url(forResource: "NameOfDataModel", withExtension: "momd")
guard let model = NSManagedObjectModel(contentsOf: modelURL!) else { fatalError("model not found") }

2) Initialize and configure an instance of NSPersistentStoreCoordinator with the NSManagedObjectModel instance and an NSPersistentStoreType

The reason you create the NSManagedObjectModel instance first is because the next step depends on it. NSPersistentStoreCoordinator will use your NSManagedObjectModel instance to configure itself and prepare to create the correct kind of persistent store based on what NSPersistentStoreType you tell it to use.

In the code example that follows, I used NSSQLiteStoreType to create a SQLite persistent store. Regardless of what kind of persistent store you use though, NSPersistentStoreCoordinator needs your managed object model instance.

1
2
3
4
5
6
7
8
9
// Configure NSPersistentStoreCoordinator with an NSPersistentStore
let psc = NSPersistentStoreCoordinator(managedObjectModel: model)

let storeURL = try! FileManager
        .default
        .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent("NameOfDataModel.sqlite")

try! psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)

3) Initialize an instance of NSManagedObjectcontext and assign it the NSPersistentStoreCoordinator instance

The last step is to initialize an instance of NSManagedObjectContext and assign it the NSPersistentStoreCoordinator instance.

1
2
3
// Create and return NSManagedObjectContext
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = psc

Putting it all together – creating the Core Data stack in code

When I create the Core Data stack, I like to encapsulate the code in a stand-alone function that returns an instance of NSManagedObjectContext.

Here are all three steps put together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func createMainContext() -> NSManagedObjectContext {
    
    // Initialize NSManagedObjectModel
    let modelURL = Bundle.main.url(forResource: "NameOfDataModel", withExtension: "momd")
    guard let model = NSManagedObjectModel(contentsOf: modelURL!) else { fatalError("model not found") }
    
    // Configure NSPersistentStoreCoordinator with an NSPersistentStore
    let psc = NSPersistentStoreCoordinator(managedObjectModel: model)

    let storeURL = try! FileManager
            .default
            .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            .appendingPathComponent("NameOfDataModel.sqlite")
    
    try! psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)
    
    // Create and return NSManagedObjectContext
    let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    context.persistentStoreCoordinator = psc
    
    return context
}
Once you’ve got a function like createMainContext(), you’ll be able to call it to obtain a fully-configured NSManagedObjectContext instance.

I highly recommend you avoid calling it inside any of your view controllers. Instead, my recommendation is to call it to obtain your NSManagedObjectContext instance inside of the AppDelegate's application(_:didFinishLaunchingWithOptions:) function. From there, you can pass it to your first view controller, and from that first view controller on to other view controllers that need it through prepare(for segue:sender:).

For more on this “dependency injection” strategy, or if you’re more of a visual learner, check out my Pluralsight course, Core Data Fundamentals with Swift!

comments powered by Disqus