Created
September 13, 2017 08:34
-
-
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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