Post

Replies

Boosts

Views

Created

SwiftData: Migrate from un-versioned to versioned schema
I've realized that I need to use migration plans, but those required versioned schemas. I think I've updated mine, but I wanted to confirm if this was the proper procedure. To start, none of my models were versioned. I've since wrapped them in a VersionedSchema like this: enum TagV1: VersionedSchema { static var versionIdentifier: Schema.Version = .init(1, 0, 0) static var models: [any PersistentModel.Type] { [Tag.self] } @Model final class Tag { var id = UUID() var name: String = "" // Relationships var transactions: [Transaction]? = nil init(name: String) { self.name = name } } } I also created a type alias to point to this. typealias Tag = TagV1.Tag This is what my container looks like in my app file. var sharedModelContainer: ModelContainer = { let schema = Schema([ Tag.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() The application builds and run successfully. Does this mean that my models are successfully versioned now? I'm trying to avoid an error I came across in earlier testing. That occurred because none of my models were versioned and I tried to setup a migration plan Cannot use staged migration with an unknown coordinator model version.
0
1
401
Nov ’24
SwiftData: How can I tell if a migration went through successfully?
I created 2 different schemas, and made a small change to one of them. I added a property to the model called "version". To see if the migration went through, I setup the migration plan to set version to "1.1.0" in willMigrate. In the didMigrate, I looped through the new version of Tags to check if version was set, and if not, set it. I did this incase the willMigrate didn't do what it was supposed to. The app built and ran successfully, but version was not set in the Tag I created in the app. Here's the migration: enum MigrationPlanV2: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [DataSchemaV1.self, DataSchemaV2.self] } static let stage1 = MigrationStage.custom( fromVersion: DataSchemaV1.self, toVersion: DataSchemaV2.self, willMigrate: { context in let oldTags = try? context.fetch(FetchDescriptor<DataSchemaV1.Tag>()) for old in oldTags ?? [] { let new = Tag(name: old.name, version: "Version 1.1.0") context.delete(old) context.insert(new) } try? context.save() }, didMigrate: { context in let newTags = try? context.fetch(FetchDescriptor<DataSchemaV2.Tag>()) for tag in newTags ?? []{ if tag.version == nil { tag.version = "1.1.0" } } } ) static var stages: [MigrationStage] { [stage1] } } Here's the model container: var sharedModelContainer: ModelContainer = { let schema = Schema(versionedSchema: DataSchemaV2.self) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer( for: schema, migrationPlan: MigrationPlanV2.self, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() I ran a similar test prior to this, and got the same result. It's like the code in my willMigrate isn't running. I also had print statements in there that I never saw printed to the console. I tried to check the CloudKit console for any information, but I'm having issues with that as well (separate post). Anyways, how can I confirm that my migration was successful here?
0
1
361
Nov ’24
SwiftData Migration: Keeps failing at the end of willMigrate
I've been trying to setup a successful migration, but it keeps failing with this error: NSCloudKitMirroringDelegate are not reusable and should have a lifecycle tied to a given instance of NSPersistentStore. I can't find any information about this online. I added breakpoints throughout the code in willMigrate, and it originally failed on this line: try? context.save() I removed that, and it still failed. After I reload the app, it doesn't run the migration again and the app loads successfully. I figured since it crashed, it would keep trying, but I guess not. Here's how my migration is setup. enum MigrationV1ToV2: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] } static var stages: [MigrationStage] { [stage] } static let stage = MigrationStage.custom( fromVersion: SchemaV1.self, toVersion: SchemaV2.self, willMigrate: { context in // Get cycles let cycles = try? context.fetch(FetchDescriptor<SchemaV1.Cycle>()) if let cycles { for cycle in cycles { // Create new recurring objects based on what's in the cycle for income in cycle.income { let recurring = SchemaV2.Recurring(name: income.name, frequency: income.frequency, kind: .income) recurring.addAmount(.init(date: cycle.startDate, amount: income.amount)) context.insert(recurring) } for expense in cycle.expenses { let recurring = SchemaV2.Recurring(name: expense.name, frequency: expense.frequency, kind: .expense) recurring.addAmount(.init(date: cycle.startDate, amount: expense.amount)) context.insert(recurring) } for savings in cycle.savings { let recurring = SchemaV2.Recurring(name: savings.name, frequency: savings.frequency, kind: .savings) recurring.addAmount(.init(date: cycle.startDate, amount: savings.amount)) context.insert(recurring) } for investment in cycle.investments { let recurring = SchemaV2.Recurring(name: investment.name, frequency: investment.frequency, kind: .investment) recurring.addAmount(.init(date: cycle.startDate, amount: investment.amount)) context.insert(recurring) } } //try? context.save() } else { print("The cycles were not able to be fetched.") } }, didMigrate: { context in // Get new recurring objects let newRecurring = try? context.fetch(FetchDescriptor<SchemaV2.Recurring>()) if let newRecurring { for recurring in newRecurring { // Get all recurring with the same name and kind let sameName = newRecurring.filter({ $0.name == recurring.name && $0.kind == recurring.kind }) // Add amount history to recurring object, and then remove matching for match in sameName { recurring.amountHistory.append(contentsOf: match.amountHistory) context.delete(match) } } //try? context.save() } else { print("The new recurring objects could not be fetched.") } } ) } Here's is my modelContainer in the app file. There is a fatal error occurring here that's crashing the app. var sharedModelContainer: ModelContainer = { let schema = Schema(versionedSchema: SchemaV2.self) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer( for: schema, migrationPlan: MigrationV1ToV2.self, configurations: [modelConfiguration] ) } catch { fatalError("Could not create ModelContainer: \(error)") } }() Does anyone have any suggestions for this? EDIT: I found this error in the console that may be relevant. BUG IN CLIENT OF CLOUDKIT: Registering a handler for a CKScheduler activity identifier that has already been registered (com.apple.coredata.cloudkit.activity.export.8F7A1261-4324-40B4-B041-886DF36FBF0A). CloudKit setup failed because it couldn't register a handler for the export activity. There is another instance of this persistent store actively syncing with CloudKit in this process. And here is the fatal error Fatal error: Could not create ModelContainer: SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer, _explanation: nil)
2
3
613
Nov ’24