I’ve realized I need id’s for all of my Core Data entities to fetch by those id’s. As the NSManagedObjectID
can change, I want to add a custom id attribute of the type UUID
.
First I planned to add the id as a non-optional UUID. Though, as CloudKit seems to require that all attributes are optional anyway, it seems like a good idea to mark this new UUID as optional (should I add iCloud sync later). This makes it also eligible for a lightweight Core Data migration.
Even though, the id is marked optional in the Core Data Model, I want to ensure all of the entities (both existing and new ones) actually have an id/UUID saved!
How do I populate the new UUID attribute during the Core Data migration? Do I have to use a manual migration for this?
After testing the approach by Tom Harrington, I couldn’t find out why my mapping model isn’t working for some unknown reason. I’ve redone it so many times and checked every possible part.
Nevertheless, I’ve decided to go for a lightweight migration now. It’s recommended by Apple anyway and this is future proof for adding CloudKit support later on. As CloudKit
requires attributes to be optional or have a default value set, if non-optional. As both options don’t really make sense for an ID, I have to handle this myself to ensure the id’s are unique anyway.
I’m basically following Michael Tsai’s suggestion now, but wanted to elaborate a little further.
First I’ve created a new version of my Core Data Stack via Editor > Add Model Version… and edited the new version.
Next I’ve set the active Core Data Stack to the new version via the editor sidebar. Besides that I’ve also set a model version identifier in in the sidebar:
To catch the database update and perform the actual migration I’ve added a custom implementation when creating my Core Data Controller.
container = NSPersistentContainer(name: "Migration_Test")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Core Data Loading Error: \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
// MANUAL MIGRATION (if needed)
// Get User Default for Last Migration Version (0 if not set)
let lastMigration = UserDefaults.standard.integer(forKey: "lastMigrationVersion")
// Get Core Data Model Version Identifier (Set in Sidebar)
let modelIdentifier = container.viewContext.persistentStoreCoordinator?.managedObjectModel.versionIdentifiers.first as? String
let currentModelVersion = Int(modelIdentifier ?? "") ?? 0
// Check if migration is needed
if lastMigration < currentModelVersion {
do {
// Perform Actual Migration
try ManualMigration().migrate(from: lastMigration, to: currentModelVersion, in: container.viewContext)
// Save Latest Migration Version to User Defaults
UserDefaults.standard.set(currentModelVersion, forKey: "lastMigrationVersion")
} catch {
print("Failed Migration to Version \(currentModelVersion)")
}
}
Inside my ManualMigration().migrate()
class/function I simply check which version I want to migrate to. In my case I’ve then iterated trough all my entities that changed via a fetch request that fetches all items of each give entity and saves a new UUID.