In my Swift app I had a data model which I needed to remodel.
After that, at the root level of my app, the code crashes when trying to load the ModelContainer
, no matter what.
The error is a huge system report that starts with this:
addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (134110)
struct myApp: App {
init() {
do {
container = try ModelContainer(for: Role.self) //**ERROR HERE**
let context = ModelContext(container)
deleteData(context: context) //IRRELEVANT AT THIS TIME
insertHardcodedRoles(context: context) // IRRELEVANT AT THIS TIME
} catch {
fatalError("Failed to initialize ModelContainer: \(error)")
}
}
}
deleteData(context:context)
after insertHarcodedRoles(context: context)
to make sure data was erased after loading. Didn't help.The container itself seems to have a problem. Apparently you can't "easily" alter your SwiftData model?
You can't simply change your @Model and then expect all is going to work magically. Instead you need to make a SchemaMigrationPlan with VersionedSchema. It essentially means, whenever you make an update on your data model, create a new one, keep track of all created models, and provide a "transition"/"migration" between all versions if necessary.
This is an excellent resource to learn it quickly and thoroughly.
But here is an example. Consider you have this Model:
@Model
final class Person
var name: String
and you want to change that to:
@Model
final class Person
var name: String
var age: Int
So first you need to create "numbered" models:
enum schemaV1: VersionedSchema {
static var models: [any Persistentmodel.Type] {
[Person.self]
}
static var versionIdentifier: Schema.Version = Schema.Version(1, 0, 0)
@Model
final class Person
var name: String
}
enum schemaV2: VersionedSchema {
static var models: [any Persistentmodel.Type] {
[Person.self]
}
static var versionIdentifier: Schema.Version = Schema.Version(1, 1, 0)
//or whatever version number you think fits
@Model
final class Person
var name: String
var age: Int
}
and second you need to "transition/migrate"
enum PersonMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
//there is also .lightweight(..) migration, read the link above
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: { context in
let persons = try? context.fetch(FetchDescriptor<SchemaV1.Trip>())
// Do your update here, like giving a default age
for person in person {
updatedPerson = Person(name: person.name, age: 25)
context.insert(updatedPerson)
}
try? context.save()
}, didMigrate: nil
)
}
Third, when creating the container at root level, you can tell it to adopt the migration plan:
struct myApp: App {
let container = ModelContainer(
for: Person.self,
migrationPlan: PersonMigrationPlan.self
)
...
}
Fourth, you need to make sure that all views or files that accessed or worked with the data, adjust to the existence of that new age property.
Finally, clean build folder, maybe reset the simulator device, build, run.
Thanks @Sweeper for pointing me to Migration.