Migrating to NSPersistentContainer

Update - 2017/07/12

if let oldUrl = oldUrl {
        let description = NSPersistentStoreDescription(url: oldUrl)
        persistentContainer.persistentStoreDescriptions = [description]
    }
    persistentContainer.loadPersistentStores { (description, error) in
        if let oldUrl = self.oldUrl {
        do {
            let psc = persistentContainer.persistentStoreCoordinator
            let store = psc.persistentStores[0]
            try psc.migratePersistentStore(store, to: url, options: nil, withType: NSSQLiteStoreType)                                                   self.deleteDatabase(url: oldUrl)
            self.cleanDb()
        } catch {
            block(description, error)
            return
        }
    }

Small update - instead of replacing, I use migratePersistentStore instead. This seems to work a little better with the current setup. So I have an oldUrl which is returned if the old store exists. If it's not nil I create the database against this and then migrate it to the correct location under Application Support. The old database still needs to be deleted in a separate step (assuming you no longer need it).


iOS 10 brought some welcome changes to Core Data and I need to migrate an existing database into this. Here’s how I did it.

First off, my existing DB is in the Documents folder. That’s not really good practice and NSPersistentContainer will create one in Application Support. That seems more sensible so I decided to move my Database. The default SQLite backed Core Data store includes SHM and WAL files (Write-Ahead Logging). To ensure you don’t lose data you need to move all three files at the same time. Fortunately, NSPersistentStoreCoordinator has built-in support for moving a database around.

First, I set up the NSPersistentStoreContainer:

persistentContainer = NSPersistentContainer(name: "GameModel")
persistentContainer.loadPersistentStores { (description, error) in
    if let error = error {
        fatalError("Could not create CoreData store: \(error)")
    }
    print(description)
}

This gives me something to work against. NSPersistentStoreContainer will create a NSPersistentStoreCoordinator with an sqlite DB at ApplicationSupport/GameModel.sqlite upon calling loadPersistentStores(completionHandler:). I want to replace that with the old Documents/Core_Data.sqlite. Fortunately, there’s a replace function right there.

let psc = persistentContainer.persistentStoreCoordinator
guard let storeUrl = psc.persistentStores.first?.url else {
    return
}
do {
    try psc.replacePersistentStore(at: storeUrl,
                                   destinationOptions: nil,
                                   withPersistentStoreFrom: oldUrl,
                                   sourceOptions: nil,
                                   ofType: NSSQLiteStoreType)
    persistentContainer.loadPersistentStores(completionHandler: { (description, error) in
        if let error = error {
            fatalError("Could not create CoreData store: \(error)")
        }
        print(description)
    })
} catch {
    print("Could not replace store: \(error)")
}

The newly created GameModel sqlite DB is replaced with the contents of the existing database by the replace call. I then need to call loadPersistentStores again to set up the NSPersistentContainer against the updated DB. Once it’s successful, I can delete the old DB files. There might be a better way, but this worked for me:

private func deleteOld(url: URL) {
        let parent = url.deletingLastPathComponent()
        let name = url.lastPathComponent
        do {
            try FileManager.default.contentsOfDirectory(at: parent, includingPropertiesForKeys: nil, options: [])
                .filter {
                    $0.lastPathComponent.hasPrefix(name)
                }
                .forEach {
                    try FileManager.default.removeItem(at: $0)
                }
        } catch {
            print("Failed to clear old DB: \(error)")
        }
    }

Where the passed URL is the old store URL.