Core Data in Swift Part 1 - The Stack

While updating a new Core Data app to Swift 2 I've found a few situations where either there doesn't seem to be any official guidance on best practices, or worse, what I'd consider bad recommendations. Over a few posts I hope to document the questions I've run into, the solutions I've found, and ultimately, what I end up considering to be a good way to build a Core Data app in Swift.

Before I start I'm going to assume you've already read the Core Data Programming Guide. If not, you should definitely read that first as I'm not going to touch on even a fraction of the topics it covers.

One additional note; While the code in this first post is all going to be in Swift there's nothing here you couldn't implement in Objective-C.

We Need Some Context

The Managed Object Context is the class you'll interact with most often when using Core Data. The Apple Guide has a good chapter on the objects that make up "the stack" that sits between the Context and the underlying persistent store. It also includes some sample code for setting up a stack but it stops short of showing you how to actually use it in your app.

If you read the sample code provided by Apple you'll notice that the initializer of their DataController class adds the persistent store to the coordinator asynchronously. This is important and I'm surprised how often I see Core Data code that adds the store on the main thread. Not only does this require accessing the disk (unless it's an in-memory store), there is no way to know how long this operation is going to take to complete. Depending on the number of pending migrations and the amount of user data you can easily cause your entire app to lock up, or worse, fail to launch in time.

So accepting that adding the persistent store on a background thread is a good idea this leaves us with a problem. Let's say we're using their DataController class in our app:

let dataController = DataController()
let managedObjectContext = dataController.managedObjectContext
// Do stuff with the context
managedObjectContext.save()

Ignoring the missing error handling, there's a problem with this code that's not immediately obvious if you haven't read the DataController class. Since the init method dispatches to a background queue to set up the persistent store the persistent store controller is not going to have any persistent stores when init finishes. This means our code is going to throw an exception like this:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores (unknown).  It cannot perform a save operation.'

Isn't asynchronous programming fun?

Ok, so we need to make sure we wait until the store coordinator has finished trying to add the persistent store before we attempt to actually use the context. There are a few ways we can do this. One very Objective-C way would be to use the delegate pattern and notify our delegate when the background code succeeds or fails. Another, more Swifty way, would be to supply a callback to our init method.

func init(callback: NSError -> ()) {
    // ...
}

This approach would probably work well for a simple app but it has a couple of drawbacks. For one, We can only supply one callback. Any code that needs to execute after the context is ready is going to need to be executed from within that callback. Additionally, this code also doesn't do anything to prevent us from trying to use the managed object context before it's ready.

Alright. So how can we work around those two issues? How can we supply an arbitrary number of operations that are guaranteed to execute only after the store is ready and make it hard to accidentally use our managed object context before that?

I'm Super Serial

Serial dispatch queues are great for executing code in the background that needs to happen in a specific order. By using our own serial queue instead of whatever global queue GCD gives us we can ensure that background initialization completes before any other code on that queue is executed.

Here's our new init method:

    private let queue: dispatch_queue_t

    init(modelUrl: NSURL, storeUrl: NSURL, concurrencyType: NSManagedObjectContextConcurrencyType = .MainQueueConcurrencyType) {

        guard let modelAtUrl = NSManagedObjectModel(contentsOfURL: modelUrl) else {
            fatalError("Error initializing managed object model from URL: \(modelUrl)")
        }
        managedObjectModel = modelAtUrl

        persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

        _managedObjectContext = NSManagedObjectContext(concurrencyType: concurrencyType)
        _managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

        let options = [
            NSMigratePersistentStoresAutomaticallyOption: true,
            NSInferMappingModelAutomaticallyOption: true
        ]

        print("Initializing persistent store at URL: \(storeUrl.path!)")

        let dispatch_queue_attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0)
        queue = dispatch_queue_create("DataStoreControllerSerialQueue", dispatch_queue_attr)

        dispatch_async(queue) {
            do {
                try self.persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeUrl, options: options)
            } catch let error as NSError {
                print("Unable to initialize persistent store coordinator:", error)
                self.error = error
            } catch {
                fatalError()
            }
        }
    }

So now we're using our own serial queue to set up the store. It may not be obvious yet how this helps us. Next let's look at how our class stores the managed object context.

    private var _managedObjectContext: NSManagedObjectContext

    var managedObjectContext: NSManagedObjectContext? {
        guard let coordinator = _managedObjectContext.persistentStoreCoordinator else {
            return nil
        }
        if coordinator.persistentStores.isEmpty {
            return nil
        }
        return _managedObjectContext
    }

Instead of a public managedObjectContext instance variable we have a private internal variable and a public accessor that returns an optional. This way we never return an invalid context to any of our clients. Now all we have to do is let our clients request access to the managed object context.

    func inContext(callback: NSManagedObjectContext? -> Void) {
        // Dispatch the request to our serial queue first and then back to the context queue.
        // Since we set up the stack on this queue it will have succeeded or failed before
        // this block is executed.
        dispatch_async(queue) {
            guard let context = self.managedObjectContext else {
                callback(nil)
                return
            }

            context.performBlock {
                callback(context)
            }
        }
    }

Hopefully this code is pretty clear. I've added a method that takes a callback and wraps that function in a block which is passed to our serial queue. Because the setup code is added to that queue as soon as it's created and the queue will only execute one block at a time we can be sure that this code will be executed after the controller has had a chance to try and load the store. When it's run the block checks to make sure we have a valid context and if so, executes the callback on the context's queue. This way we can help make sure our access to the managed object context is happening on the right thread.

Here's the complete class:

//
//  DataStoreController.swift
//

import CoreData

class DataStoreController {

    private var _managedObjectContext: NSManagedObjectContext

    var managedObjectContext: NSManagedObjectContext? {
        guard let coordinator = _managedObjectContext.persistentStoreCoordinator else {
            return nil
        }
        if coordinator.persistentStores.isEmpty {
            return nil
        }
        return _managedObjectContext
    }

    let managedObjectModel: NSManagedObjectModel
    let persistentStoreCoordinator: NSPersistentStoreCoordinator

    var error: NSError?

    func inContext(callback: NSManagedObjectContext? -> Void) {
        // Dispatch the request to our serial queue first and then back to the context queue.
        // Since we set up the stack on this queue it will have succeeded or failed before
        // this block is executed.
        dispatch_async(queue) {
            guard let context = self.managedObjectContext else {
                callback(nil)
                return
            }

            context.performBlock {
                callback(context)
            }
        }
    }

    private let queue: dispatch_queue_t

    init(modelUrl: NSURL, storeUrl: NSURL, concurrencyType: NSManagedObjectContextConcurrencyType = .MainQueueConcurrencyType) {

        guard let modelAtUrl = NSManagedObjectModel(contentsOfURL: modelUrl) else {
            fatalError("Error initializing managed object model from URL: \(modelUrl)")
        }
        managedObjectModel = modelAtUrl

        persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

        _managedObjectContext = NSManagedObjectContext(concurrencyType: concurrencyType)
        _managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

        let options = [
            NSMigratePersistentStoresAutomaticallyOption: true,
            NSInferMappingModelAutomaticallyOption: true
        ]

        print("Initializing persistent store at URL: \(storeUrl.path!)")

        let dispatch_queue_attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0)
        queue = dispatch_queue_create("DataStoreControllerSerialQueue", dispatch_queue_attr)

        dispatch_async(queue) {
            do {
                try self.persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeUrl, options: options)
            } catch let error as NSError {
                print("Unable to initialize persistent store coordinator:", error)
                self.error = error
            } catch {
                fatalError()
            }
        }
    }
}

Not much else to it. If there's an error I store it as an instance variable so that the client code can reference it if needed. Here's an example of how it's used:

let libraryDirectoryUrl = NSFileManager.defaultManager().URLsForDirectory(.LibraryDirectory, inDomains: .UserDomainMask).first!
let storeUrl = libraryDirectoryUrl.URLByAppendingPathComponent("MyStore.sqlite")

guard let modelUrl = NSBundle.mainBundle().URLForResource("MyModel", withExtension: "momd") else {
    fatalError("Error loading Core Data model")
}

let dataStoreController = DataStoreController(modelUrl: modelUrl, storeUrl: storeUrl)
dataStoreController.inContext { context in
    guard let context = context else {
        print("Unable to load context:", dataStoreController.error)
        return
    }

    // Do stuff with context
}

// Elsewhere
dataStoreController.inContext { context in
    guard let context = context else {
        print("Unable to load context:", dataStoreController.error)
        return
    }

    // Do other stuff with context
}

One possible modification to this code would be to make the DataStoreController a singleton class. This could be achieved by simply adding a static constant sharedInstance and making the init private. I'll leave it up to you as to whether you think singletons are always a bad idea but I've used this code both ways and this is one situation I think it can make sense depending on the structure of your app.

In the next post I plan to look at NSManagedObject subclasses in Swift.

"Core Data in Swift Part 1 - The Stack" was originally published on 29 Aug 2015.