Core Data L5.3.1

Core data is the framework to help manage the model aspect of an application.

Core Data gives us a way to manage the important tangible and essential pieces of an app of and gives these pieces the tile Managed Objects

core data model

data stack

Managing the Managed Objects entails everything from changing them, tracking them, persisting them, and managing their relationships etc. i.e. a character of a game app.

Core Data creates a visual mapping (using the Xcode tool) between the database and the objects that represent whats in the database columns. It also allows us to query for those objects without having to write any SQL.

Core Data Stack

Several Core Data objects, all with different responsibilities, come together to form a stack that works like a single unit.

data stack

Object Model

(aka "Managed Object Model", or "Data Model")

This is the physical representation of the model and exists in xcode as a special kind of graphics file similar to a storyboard file but with a different purpose and capabilities.

It's used to specify the app's classes and relationships that will managed within the app and is also what the rows and tables of the app's sqlite database will be generated from.

Managed Object

Managed objects are the important pieces we touched on like a character of a game app.

Much like how interface builder automates a storyboard file Core Data automates a managed object model file. Manages objects are what's specified by the Object Model and are responsible for most of Core Data's magic. While the inherit from class ManagedObject and look like regular swift objects they have a substantial feature that makes them different - they save the contents of their properties to a database file. Core data hides this complexity from us so we don't have to worry about it (magic).

Every single object in the model will be a subclass of ManagedObject.

Context

(aka "Managed Object Context")

This is kind of like a place. This is where the managed objects live until they are saved to disk, and where nearly every Core Data operation takes place - creating, deleting, saving, etc this is all done by asking the context to perform the action.

Fetch Request

To find out who and what lives in the Context a fetch request is needed.

A Fetch Request is a search of the Context for a certain type of Managed Object. It can specify the type of object, a filter and how the resulting objects should be sorted.

Fetch Results Controller

This piece of the Core Data Stack is actually part of the controller layer.

A Fetch Results Controller works in part to control how the data from the Fetch Request is displayed in a view. It takes a Fetch Request runs the search and help display the results in a table view or with a little extra work on the programmers part a collection view.

The most basic task a Fetch Results Controller must perform is to keep the model and view in sync. If the objects returned from the Fetch Request ever change the Fetch Results Controller automagically updates the table view.

Stores

At the lower level Core Data relies on what's call "Persistent Stores", or Stores for short.

Stores are where Managed Objects actually get stored when saved to disk. They can be thought of as database data files and can exist in several types, namely XML, binary format, and SQLite. SQLite is the most common.

You can have more than one Store or type of Store in any given app.

Store Coordinator

This object is what allows us to have multiple Stores and keep the rest of the app from knowing about them.

For the most part Stores and their Store Coordinators are not something we have to worry about too much.

A Class To Manage The Stack

We keep all the members of the Core Data Stack in one class. This is the boilerplate code available on the apple developer site, and although we won't interact directly with this file much and it's mostly obsolete as Xcode does a lot of this under the hood automatically, but it's important to understand so we know how the pieces fit together.

The initializer reads the model file and get's it's extension once it has been compiled by Xcode.

It creates a sqlite store for the model inside the Documents folder and will call it model.sqlite. Once it's done with that it initializes a Context and connects all the necessary parts into a Persistent Store Coordinator which will perform migrations.

If everything goes as planned, and there are no errors we have a all the Core Data Stack built and ready to work with.

import CoreData

// MARK: - CoreDataStack

struct CoreDataStack {

    // MARK: Properties

    private let model: NSManagedObjectModel
    internal let coordinator: NSPersistentStoreCoordinator
    private let modelURL: URL
    internal let dbURL: URL
    let context: NSManagedObjectContext

    // MARK: Initializers

    init?(modelName: String) {

        // Assumes the model is in the main bundle
        guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "momd") else {
            print("Unable to find \(modelName)in the main bundle")
            return nil
        }
        self.modelURL = modelURL

        // Try to create the model from the URL
        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            print("unable to create a model from \(modelURL)")
            return nil
        }
        self.model = model

        // Create the store coordinator
        coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)

        // create a context and add connect it to the coordinator
        context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        context.persistentStoreCoordinator = coordinator

        // Add a SQLite store located in the documents folder
        let fm = FileManager.default

        guard let docUrl = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
            print("Unable to reach the documents folder")
            return nil
        }

        self.dbURL = docUrl.appendingPathComponent("model.sqlite")

        // Options for migration
        let options = [NSInferMappingModelAutomaticallyOption: true,NSMigratePersistentStoresAutomaticallyOption: true]

        do {
            try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: options as [NSObject : AnyObject]?)
        } catch {
            print("unable to add store at \(dbURL)")
        }
    }

    // MARK: Utils

    func addStoreCoordinator(_ storeType: String, configuration: String?, storeURL: URL, options : [NSObject:AnyObject]?) throws {
        try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: dbURL, options: nil)
    }
}

// MARK: - CoreDataStack (Removing Data)

internal extension CoreDataStack  {

    func dropAllData() throws {
        // delete all the objects in the db. This won't delete the files, it will
        // just leave empty tables.
        try coordinator.destroyPersistentStore(at: dbURL, ofType:NSSQLiteStoreType , options: nil)
        try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: nil)
    }
}

// MARK: - CoreDataStack (Save Data)

extension CoreDataStack {

    func saveContext() throws {
        if context.hasChanges {
            try context.save()
        }
    }

    func autoSave(_ delayInSeconds : Int) {

        if delayInSeconds > 0 {
            do {
                try saveContext()
                print("Autosaving")
            } catch {
                print("Error while autosaving")
            }

            let delayInNanoSeconds = UInt64(delayInSeconds) * NSEC_PER_SEC
            let time = DispatchTime.now() + Double(Int64(delayInNanoSeconds)) / Double(NSEC_PER_SEC)

            DispatchQueue.main.asyncAfter(deadline: time) {
                self.autoSave(delayInSeconds)
            }
        }
    }
}