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.