Skip to content

Instantly share code, notes, and snippets.

@horseshoe7
Created September 13, 2017 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save horseshoe7/0083ce48cba98ae107a18de06a39767f to your computer and use it in GitHub Desktop.
Save horseshoe7/0083ce48cba98ae107a18de06a39767f to your computer and use it in GitHub Desktop.
Initialization of Core Data stack that needs a manual migration from 2 different models and stores:
// see also this gist: https://gist.github.com/horseshoe7/e85bbb90278f626bea2f827ee5228703
// and a post about Core Data migrations at http://horseshoe7.wordpress.com
import Foundation
import CoreData
class CoreDataStackHelper : NSObject, MHWMigrationManagerDelegate {
var currentModelStoreURL: URL? = nil
func initializePersistentContainer(completion: ((_ container: NSPersistentContainer) -> Void)?) {
let container = NSPersistentContainer(name: "SongbookSimpleDataModel") // your newest model you want to migrate your legacy store to!
assert(container.persistentStoreDescriptions.count <= 1, "Never thought there would be more than one Persistent Store Description for this data model!")
for pscDescription in container.persistentStoreDescriptions {
self.currentModelStoreURL = pscDescription.url
}
// here you need to work on a "Migration Stack"
if self.requiresLegacyCoreDataMigration() {
do {
try self.migrateLegacyModelToCurrent(using: container)
self.migrationDidComplete()
} catch {
}
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
// we add this so that any background work gets merged!
container.viewContext.automaticallyMergesChangesFromParent = true
completion?(container)
})
}
fileprivate func migrationDidComplete() {
let storeURL = self.legacyCoreDataStoreURL
let storeURLDirectory = storeURL.deletingLastPathComponent()
var isDir = ObjCBool(false)
if FileManager.default.fileExists(atPath: storeURLDirectory.path, isDirectory: &isDir) {
if isDir.boolValue {
do {
try FileManager.default.removeItem(atPath: storeURLDirectory.path)
} catch {
}
}
}
}
// this is custom to your application!
fileprivate var legacyCoreDataStoreURL: URL {
// I had to install and old version then inspect the file structure. This is very specific to your app
if let documentsURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last {
var storeURL = documentsURL.appendingPathComponent("SongbookSimpleCoreData/")
storeURL = storeURL.appendingPathComponent("SongbookSimple.sqlite")
return storeURL
}
fatalError("You are fetching the url incorrectly. This method should never fail")
}
fileprivate func requiresLegacyCoreDataMigration() -> Bool {
return FileManager.default.fileExists(atPath: self.legacyCoreDataStoreURL.path)
}
// this is mostly custom to your application
fileprivate func migrateLegacyModelToCurrent(using container: NSPersistentContainer) throws {
let momdName = "SongbookSimple" //pass this as a parameter. Legacy Model Name
guard let modelURL = Bundle(for: type(of: self)).url(forResource: momdName, withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
guard let _ = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let storeURL = self.legacyCoreDataStoreURL
let migrationManager = MHWMigrationManager()
migrationManager.delegate = self
let migrationOptions: [AnyHashable: Any]? = nil
do {
try migrationManager.progressivelyMigrateURL(storeURL, ofType: NSSQLiteStoreType, options: migrationOptions, to: container.managedObjectModel)
} catch {
log.error("Error During Migration: \(error.localizedDescription)")
throw error
}
}
func destinationURLOfFullyMigratedStoreRequested(by migrationManager: MHWMigrationManager) -> URL? {
return self.currentModelStoreURL
}
func migrationManager(_ migrationManager: MHWMigrationManager, migrationProgress: Float) {
log.info(String(format:"Migration Progress: %.2f", migrationProgress))
}
// this is to help the migration manager if it can't resolve these on its own
func migrationManager(_ migrationManager: MHWMigrationManager, mappingModelsForSourceModel sourceModel: NSManagedObjectModel) -> [Any] {
var mappingModels: [NSMappingModel] = []
guard let modelName = sourceModel.mhw_modelName() else {
log.error("Could not derive the model name from the source model!")
return []
}
if modelName == "SongbookSimple 3" {
// migrate old store to old store
if let urls = Bundle(for: type(of: self)).urls(forResourcesWithExtension: "cdm", subdirectory: nil) {
for url in urls {
if let foundRange = url.lastPathComponent.range(of: "Migration_3-to_4"), foundRange.isEmpty == false {
if let mappingModel = NSMappingModel(contentsOf: url) {
if let userRange = url.lastPathComponent.range(of: "User"), userRange.isEmpty == false {
mappingModels.insert(mappingModel, at: 0)
} else {
mappingModels.append(mappingModel)
}
}
}
}
}
} else if modelName == "SongbookSimple 4" {
// migrate old model to new model
if let urls = Bundle(for: type(of: self)).urls(forResourcesWithExtension: "cdm", subdirectory: nil) {
for url in urls {
if let foundRange = url.lastPathComponent.range(of: "Migration-Legacy-To-Modern"), foundRange.isEmpty == false {
if let mappingModel = NSMappingModel(contentsOf: url) {
if let userRange = url.lastPathComponent.range(of: "User"), userRange.isEmpty == false {
mappingModels.insert(mappingModel, at: 0)
} else {
mappingModels.append(mappingModel)
}
}
}
}
}
}
return mappingModels
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment