Every time we launch Notes, the persistent store is seeded with data. That's not what we want as it results in duplicates.

In this episode, you learn how to easily avoid duplicates and how to start with a clean slate if you decide you no longer need seed data.

Seeding Once

The solution I show you in this episode is easy to implement. To make sure the persistent store is seeded once, we take advantage of the user defaults database. We store a key-value pair in the user defaults database to remember whether the application has already been seeded with data.

Another option is to identify each record in the persistent store with a unique identifier and prevent the insertion of records with the same unique identifier. That's a common approach, but it isn't a good fit in the context of seeding the persistent store. The goal is to seed the persistent store once.

Creating an Extension

Let's start by creating an extension for the UserDefaults class. Create a new Swift file, name it UserDefaults.swift, add an import statement for Foundation, and create an extension for the UserDefaults class.

import Foundation

extension UserDefaults {

}

To avoid having string literals scattered across the codebase, we start by defining a private enum, Keys, to store the keys we plan to use in the extension. Why is this useful? It is convenient, we avoid typos, we benefit from autocompletion, and it means we don't need to use string literals in the implementation of methods and properties.

import Foundation

extension UserDefaults {

    private enum Keys {

        static let DidSeedPersistentStore = "didSeedPersistentStore"

    }

}

The reason for creating the extension for the UserDefaults class is convenience and readability. We define a class method and a class property. The class method allows us to easily update the user defaults database for a particular key.

The method is named setDidSeedPersistentStore(_:) and it accepts a boolean as its only argument. The implementation is straightforward. We ask a reference to the shared UserDefaults instance and set the value for a particular key.

import Foundation

extension UserDefaults {

    private enum Keys {

        static let DidSeedPersistentStore = "didSeedPersistentStore"

    }

    class func setDidSeedPersistentStore(_ didSeedPersistentStore: Bool) {
        UserDefaults.standard.set(didSeedPersistentStore, forKey: Keys.DidSeedPersistentStore)
    }

}

The class property is just as easy to implement. We ask a reference to the shared UserDefaults instance and ask it for the value of a particular key.

import Foundation

extension UserDefaults {

    private enum Keys {

        static let DidSeedPersistentStore = "didSeedPersistentStore"

    }

    class func setDidSeedPersistentStore(_ didSeedPersistentStore: Bool) {
        UserDefaults.standard.set(didSeedPersistentStore, forKey: Keys.DidSeedPersistentStore)
    }

    class var didSeedPersistentStore: Bool {
        return UserDefaults.standard.bool(forKey: Keys.DidSeedPersistentStore)
    }

}

This may seem like a lot of work for something as trivial as setting and getting a value from the user defaults database. While that may be true, it significantly improves the readability at the call site as we'll see in a moment.

Updating the User Defaults Database

Before we put the extension to use, we need to decide when and where we update the user defaults database. Should we do this in the SeedOperation class? While that is an option, I don't want to put the operation in charge of this task. It should only know how to seed a managed object context with data.

A better idea is to put the Core Data manager in charge of this task. We know where to update the user defaults database. But when should we update the user defaults database? What happens if the seed operation fails to push its changes to the parent managed object context of its private managed object context? Should the Core Data manager update the key-value pair in the user defaults database? The answer is "no".

This implies that the Core Data manager needs to know if the seed operation was successful. The Core Data manager should know if the seed operation was successful at pushing the seed data to its main managed object context. We can do this by passing a flag to the completion handler of the seed operation.

We first update the type alias at the top of the SeedOperation class.

import CoreData
import Foundation

class SeedOperation: Operation {

    // MARK: - Type Alias

    typealias SeedOperationCompletion = ((Bool) -> Void)

    ...

}

We also need to make some modifications to the implementation of the seed() method. Instead of catching any errors that are thrown by the save() method in the seed() method, we propagate any errors to the call site by marking the seed() method as throwing by inserting the throws keyword.

// MARK: - Helper Methods

private func seed() throws {
    ...

    // Save Changes
    try privateManagedObjectContext.save()
}

In the main() method of the SeedOperation class, we invoke the seed() method in a do-catch statement and, based on the result of the save() method, we invoke the completion handler with the result of the seed operation.

// MARK: - Overrides

override func main() {
    do {
        // Seed With Data
        try seed()

        // Invoke Completion
        completion?(true)
    } catch {
        print("Unable to Save Managed Object Context After Seeding Persistent Store (\(error))")

        // Invoke Completion
        completion?(false)
    }
}

Updating the Core Data Manager

We can now update the seed() method of the Core Data manager. we won't be passing the result of the seed operation to the notes view controller. This means we invoke the completion handler of the seed() method in the completion handler of the seed operation without passing it the result of the seed operation.

import CoreData

extension CoreDataManager {

    // MARK: - Public API

    func seed(_ completion: (() -> Void)? = nil) {
        // Initialize Operation
        let operation = SeedOperation(with: mainManagedObjectContext) { (success) in
            completion?()

            // Update User Defaults
            UserDefaults.setDidSeedPersistentStore(success)
        }

        // Add to Operation Queue
        operationQueue.addOperation(operation)
    }

}

In the completion handler of the seed operation, we pass the result to the setDidSeedPersistentStore(_:) class method of the UserDefaults class.

Avoiding Duplicates

To avoid that the persistent store is seeded multiple times, we use a guard statement in the seed() method of the CoreDataManager class to prevent the seed operation from being executed if the persistent store was previously seeded. The guard statement is a perfect fit for this use case.

import CoreData

extension CoreDataManager {

    // MARK: - Public API

    func seed(_ completion: (() -> Void)? = nil) {
        guard !UserDefaults.didSeedPersistentStore else {
            return
        }

        // Initialize Operation
        let operation = SeedOperation(with: mainManagedObjectContext) { (success) in
            completion?()

            // Update User Defaults
            UserDefaults.setDidSeedPersistentStore(success)
        }

        // Add to Operation Queue
        operationQueue.addOperation(operation)
    }

}

The class method and property we defined in the UserDefaults extension improve the readability of the implementation of the seed() method.

Removing the Persistent Store

In development, it happens occasionally that you want to remove the persistent store and start with a clean slate. You can accomplish this by removing the application from the simulator or your device. While that works, it's easier and less destructive to make a small update to the Core Data manager to take care of this.

The approach I often take is simple. I define a constant in the Core Data manager that defines whether the current persistent store needs to be removed. When the Core Data manager is about to add the persistent store to the persistent store coordinator, we destroy the persistent store.

Let's start by defining a constant in the CoreDataManager class. The value of this constant defines whether the persistent store needs to be destroyed or not.

import CoreData

final class CoreDataManager {

    // MARK: - Configuration

    private let destroyPersistentStore = false

    ...

}

Many developers wrongly assume it's fine to manually remove the persistent store from disk using the FileManager class. Manually removing a persistent store isn't something you should ever do. Let me show you why that is.

If we run the application in the simulator and inspect the contents of the Documents directory of the application's container, we can see that not one but three files were created.

Inspecting the Application's Container

The Core Data manager uses a SQLite database as its persistent store. This results in multiple files, not one. What happens if you only remove the file with the sqlite extension? It may result in unexpected behavior or even data loss.

You need to remember that the persistent store coordinator is in charge of managing the persistent store, not the developer. The recommended solution for destroying a persistent store is by invoking the destroyPersistentStore(at:ofType:options:) method of the NSPersistentStoreCoordinator class.

That's what we do in the CoreDataManager class before we add the persistent store to the persistent store coordinator.

private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    // Initialize Persistent Store Coordinator
    let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

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

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

    // URL Persistent Store
    let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName)

    if destroyPersistentStore {
        do {
            // Destroy Persistent Store
            try persistentStoreCoordinator.destroyPersistentStore(at: persistentStoreURL,
                                                                  ofType: NSSQLiteStoreType,
                                                                  options: nil)

        } catch {
            fatalError("Unable to Destroy Persistent Store")
        }
    }

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

        // Add Persistent Store
        try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                                          configurationName: nil,
                                                          at: persistentStoreURL,
                                                          options: options)

    } catch {
        fatalError("Unable to Add Persistent Store")
    }

    return persistentStoreCoordinator
}()

Because the destroyPersistentStore(at:ofType:options:) method is throwing, we wrap it in a do-catch statement. Notice that we only destroy the persistent store if the destroyPersistentStore property is set to true.

If we run the application again, we end up with an empty persistent store. If you're wondering why we don't see the seed data, then remember that we only seed the persistent store once. Once the Core Data manager has added the flag to the user defaults database, it no longer seeds the persistent store with data.

What's Next?

In the next episode, it's time to replace the hard-coded seed data with a better, more flexible alternative. The plan is to load seed data from a file located in the application bundle and seed the persistent store with it.