In the previous tutorial, we set up a Core Data stack using the NSPersistentContainer class. While this is convenient, it teaches you very little about the Core Data stack and how to set one up from scratch. In this lesson, we create a Core Data stack without relying on Xcode's Core Data template.

Setting Up the Project

Fire up Xcode and create a new project by choosing the Single View Application template.

Setting Up the Xcode Project

This time, however, we don't check the Use Core Data checkbox. This means that the Xcode project doesn't have any references to the Core Data framework. And that is the approach I always take when working with Core Data. I like to be in charge of things.

Setting Up the Xcode Project

If we would have checked the Use Core Data checkbox during the setup of the project, Xcode would have put the code for the Core Data stack in the application delegate. This is something I don't like and we won't be cluttering the application delegate with the setup of the Core Data stack.

Instead, we are going to create a separate class that is responsible for setting up and managing the Core Data stack.

Creating the Core Data Manager

Create a new group and name it Managers. Create a new Swift file in the Managers group and name the file CoreDataManager. This is the class that will be in charge of the Core Data stack of the application.

Creating the Core Data Manager

We start by adding an import statement for the Core Data framework and define the CoreDataManager class. Note that we mark it as final. The class is not intended to be subclassed.

import CoreData

final class CoreDataManager {

}

We're going to keep the implementation straightforward. The only information we are going to give the Core Data manager is the name of the data model. We create a property of type String for the name of the data model.

import CoreData

final class CoreDataManager {

    // MARK: - Properties

    private let modelName: String

}

The designated initializer of the class accepts the name of the data model as an argument.

import CoreData

final class CoreDataManager {

    // MARK: - Properties

    private let modelName: String

    // MARK: - Initialization

    init(modelName: String) {
        self.modelName = modelName
    }

}

Setting Up the Core Data Stack

To set up the Core Data stack, we need to instantiate three objects:

  • a managed object model
  • a managed object context
  • and a persistent store coordinator

Let us start by creating a lazy property for each of these objects. Each of these properties is marked as private. But notice that we only mark the setter of the managedObjectContext property as private. The managed object context of the Core Data manager needs to be accessible by the rest of the application because it is the managed object context we will be interacting with most frequently in the application.

// MARK: - Core Data Stack

private(set) lazy var managedObjectContext: NSManagedObjectContext = {}()

private lazy var managedObjectModel: NSManagedObjectModel = {}()

private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {}()

Managed Object Context

Let's start with the implementation of the managedObjectContext property. We initialize an instance of the NSManagedObjectContext class by invoking its designated initializer, init(concurrencyType:). The initializer accepts an argument of type NSManagedObjectContextConcurrencyType. We pass in mainQueueConcurrencyType, which means the managed object context is associated with the main queue or the main thread of the application. We learn more about threading later in this series. Don't worry about it for now.

private(set) lazy var managedObjectContext: NSManagedObjectContext = {
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
}()

Remember that every parent managed object context keeps a reference to the persistent store coordinator of the Core Data stack. This means we need to set the persistentStoreCoordinator property of the managed object context.

private(set) lazy var managedObjectContext: NSManagedObjectContext = {
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

    managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
}()

We return the managed object context from the closure.

private(set) lazy var managedObjectContext: NSManagedObjectContext = {
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

    managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator

    return managedObjectContext
}()

Managed Object Model

Initializing the managed object model is easy. We ask the application bundle for the URL of the data model, we use the URL to instantiate an instance of the NSManagedObjectModel class, and we return the managed object model from the closure.

private lazy var managedObjectModel: NSManagedObjectModel = {
    guard let modelURL = Bundle.main.url(forResource: self.modelName, withExtension: "momd") else {
        fatalError("Unable to Find Data Model")
    }

    guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
        fatalError("Unable to Load Data Model")
    }

    return managedObjectModel
}()

Notice that we throw a fatal error if the application is unable to find the data model in the application bundle or if the application is unable to instantiate the managed object model. Why is that?

Simple. This should never happen in production. If the data model is not present in the application bundle or the application is unable to load the data model from the application bundle, we have bigger problems to worry about.

We ask the application bundle for the URL of a resource with an momd extension. This is the compiled version of the data model. We discuss the data model in more detail later in this series.

Persistent Store Coordinator

The last piece of the puzzle is the persistent store coordinator. This is a bit more complicated. We first instantiate an instance of the NSPersistentStoreCoordinator class using the managed object model. But that is only the first step.

private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
}()

The Core Data stack is only functional once the persistent store is added to the persistent store coordinator. We start by constructing the URL of the persistent store. There are several locations for storing the persistent store. In this example, we store the persistent store in the Documents directory of the application. But you could also store it in the Library directory. You have several options.

private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

    let fileManager = FileManager.default
    let storeName = "\(self.modelName).sqlite"

    let documentsDirectoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]

    let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName)
}()

We append sqlite to the name of the data model because we are going to use a SQLite database as the persistent store.

Because adding a persistent store is an operation that can fail, we need to perform it in a do-catch statement. To add a persistent store we invoke addPersistentStore(ofType:configurationName:at:options:) on the persistent store coordinator.

It is a wordy method that accepts four arguments:

  • the type of the persistent store, SQLite in this example
  • an optional configuration
  • the location of the persistent store
  • and an optional dictionary of options

The configuration is not important for this discussion. The options dictionary is something we discuss later in this series.

do {
    try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                                      configurationName: nil,
                                                      at: persistentStoreURL,
                                                      options: nil)
} catch {
    fatalError("Unable to Load Persistent Store")
}

If the persistent store coordinator cannot find a persistent store at the location we specified, it creates one for us. If a persistent store already exists at the specified location, it is added to the persistent store coordinator.

We print the error to the console if the operation fails and we return the persistent store coordinator from the closure.

private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

    let fileManager = FileManager.default
    let storeName = "\(self.modelName).sqlite"

    let documentsDirectoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]

    let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName)

    do {
        try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                                          configurationName: nil,
                                                          at: persistentStoreURL,
                                                          options: nil)
    } catch {
        fatalError("Unable to Load Persistent Store")
    }

    return persistentStoreCoordinator
}()

We now have a working Core Data stack, but we are currently assuming that everything is working fine all the time. Later in this series, we make the Core Data manager more robust. Right now we just want to set up a Core Data stack to make sure we have something to work with.

Adding a Data Model

Before we can try out the Core Data stack, we need to add a data model to the project. Create a new group for the data model and name it Core Data. Create a new file and choose the Data Model template from the iOS > Core Data section.

Adding a Data Model

Name the file Lists and click Create.

Adding a Data Model

Notice that the extension of the data model is xcdatamodeld. This is different from the extension we used to instantiate the managed object model. The xcdatamodeld file is not present in the application itself. It is compiled into a momd file instead. Only what is absolutely essential is contained in the momd file.

Testing the Core Data Stack

Open AppDelegate.swift and instantiate an instance of the CoreDataManager class in the application(_:didFinishLaunchingWithOptions:) method. We print the value of the managedObjectContext property to the console to make sure the Core Data stack is successfully set up.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let coreDataManager = CoreDataManager(modelName: "Lists")
    print(coreDataManager.managedObjectContext)
    return true
}

Run the application in the simulator or on a physical device. In Xcode's console, you should see something like this.

<NSManagedObjectContext: 0x1701de780>

Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files for this article from GitHub.

Now that you know what Core Data is and how the Core Data stack is set up, it's time to write some code. If you're serious about Core Data, check out Mastering Core Data With Swift. We build an application that is powered by Core Data and you learn everything you need to know to use Core Data in your own projects.