Simple Swift Core Data Stack

I got tired of writing roughly the same Core Data stack code for each project I work on, so I put together a simple framework for iOS, macOS, tvOS, and watchOS which contains all the boilerplate code to setup use of Core Data in a Swift project. CRDCoreDataStack, available on GitHub, consists of a protocol and a class which can easily be integrated into any Swift project for Apple iDevices.

You might want to initialize CRDCoreDataStack from your AppDelegate and for this you might want to setup a lazy property:

// MARK: - Core Data stack

lazy var coreDataStack: CRDCoreDataStack = {

    return CRDCoreDataStack(modelName: "MyModel", delegate: self)
}()

In this example, the first time we call coreDataStack property, the stack will get initialized and the persistent store will get created. You might want to do this in the AppDelegate method application:didFinishLaunchingWithOptions:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        // Override point for customization after application launch.

        // Initialize the Core Data stack.
        _ = coreDataStack

    ...
}

Because the process of creating the store can take a little time, you don’t typically want to wait for it to finish, rather, you want to update the UI and any collections which depend on the data only after the store has been successfully initialized. And, if there were any errors during the initialization process, you want to report them to the user and update the UI appropriately when the data is unavailable.

To accomplish this, CRDCoreDataStack posts two messages through NotificationCenter:

CRDCoreDataStack.notificationInitialized
CRDCoreDataStack.notificationInitializedFailed

These notifications pretty much correspond to the actions implied by their names. The notification for CRDCoreDataStack.notificationInitialized doesn’t contain anything in the userInfo dictionary and is triggered when the Core Data stack has been successfully initialized and the persistent store is ready for use. The CRDCoreDataStack.notificationInitializedFailed notification is triggered if there was any error during the process of initializing the stack and setting up the persistent store. Its notification contains a key-value for error in the userInfo dictionary.

You might use these notifications in a view controller class that depends on the data, as an example, setting up observers on these in the viewWillAppear method:

    override func viewWillAppear(_ animated: Bool) {

        super.viewWillAppear(animated)

        NotificationCenter.default.addObserver(forName: Notification.Name(CRDCoreDataStack.notificationInitialized), object: nil, queue: OperationQueue.main) { (notification) in

            // TODO: Get the data from the store

            // Update the table
            self.tableView.reloadData()
        }

        // Set up observer for initialization failed notification.
        NotificationCenter.default.addObserver(forName: Notification.Name(CRDCoreDataStack.notificationInitializedFailed), object: nil, queue: OperationQueue.main) { (notification) in

            if let userInfo = notification.userInfo, userInfo.count > 0 {

                if let error = userInfo["error"] as? NSError {

                    // TODO: Present the error to the user.                    
                }
            }
        }
   }

The delegate parameter in the CRDCoreDataStack initializer is a reference to a class that implements the protocol CRDCoreDataStackProtocol.

The protocol CRDCoreDataStackProtocol consists of one method:

func seedDataStore()

whose purpose is to allow you to seed the database at just the right time – only when the persistent store file does not already exist on disk, and after the persistent store has been successfully initialized.

Here’s an example of implementing the CRDCoreDataStackProtocol seedDataStore method – it assumes you have setup a lazy coreDataStack property as described earlier and that you have an entity named PersonItem with a String attribute called name:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CRDCoreDataStackProtocol {

    ...

    func seedDataStore() {

        // Populate the data store when it is created the first time.
        do {

            // Use a private context to do new inserts.
            let context = self.coreDataStack.privateChildManagedObjectContext()

            // Insert three new employee records into the PersonItem table.
            var employee = PersonItem(context: context, name: "Joe")
            employee = PersonItem(context: context, name: "Tom")
            employee = PersonItem(context: context, name: "George")

            try context.save()

        } catch let error as NSError {

            log.error(error.description)
        }

        // Save the changes into the persistent store.
        self.coreDataStack.saveChanges(onError: { (error) in

            if let error = error {

                self.log.error(error.description)
            }
        })
    }

   ...
}

Notice how I’m using a temporary private context in which to do the inserts. CRDCoreDataStack provides a public method you can call to create one of these scratchpad contexts whose parent context is the main context:

public func privateChildManagedObjectContext() -> NSManagedObjectContext

The idea here is to create a private context to do edits, then save the changes to the context. This happens in the call to:

try context.save()

This doesn’t save any changes to the main context and thus to the persistent store, it only commits the changes to the private context. Later we call the CRDCoreDataStack method saveChanges:onError which actually does the work of saving the changes in the private context and the main context to the persistent store.

There is also a readonly property called mainManagedObjectContext which allows you to transact on the main context, such as for UI read operations when populating a table or collection view.

I hope you’ll find this code useful in your Swift projects.

cdisdero

Software Engineer

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax