Migrations & Working with Core Data Instruction
Working with SwiftData Migrations and Core Data
Once a SwiftData app goes into production, there may be updates over time and it may get more features that require revisions to the schema. Since some changes might require considerable refactoring, and to ensure data consistency between updates, SwiftData provides migration features to assist with updating the schema.
Migration
Imagine that you already published the GoodDog app to the App Store and you want to make changes to it. Perhaps you’ve made a few updates that required schema changes. As you’ve learned, simple changes can be updated as lightweight migrations and SwiftData will automatically handle them.
Now, imagine that some of your users skipped an update or two and, depending on the changes you made during your updates, the data store that those people have on their devices is no longer compatible. SwiftData has a feature allowing migrations from one version to the next. In fact, you can provide a migration plan to adjust the schema across multiple versions by providing the steps as migration stages.
Remember that you made a change to the schema back in lesson 2. In that case, you added BreedModel and changed the breed type, referenced in DogModel, from a String type to BreedModel type. SwiftData performed a lightweight migration. The change to the models wasn’t complex, and the data changes were simple enough for the compiler to resolve. This type of work is called a lightweight migration stage. SwiftData also has a complex migration stage, where you would need to create the logic to change the data as it moves to a newer schema design. You may need to deduplicate the data entries, move the data from one type to another, add a binary data type, or change the property name with the Attribute feature.
Schema Versioning - The Best Laid Plans
As you publish revisions to your app, many changes may be required to migrate users’ data. SwiftData has some built-in methods to help with the migrations.
The first tool is the use of VersionedSchema, which is where you’ll list the needed models. Previously, the compiler would have inferred all the models via the relationships. You’ll define a VersionedSchema for each distinct version of your app as the data model needs to change. You’ll begin by adding all of your models in an array in the first version. Each of these schemas will be designated with a distinct versioned name.
// eg.
GoodDogSchema_V01_00_00.self,
GoodDogSchema_V01_01_00.self,
GoodDogSchema_V02_00_00.self
The second tool is SchemaMigrationPlan, which you’ll use to inform SwiftData of the order of your versions. SwiftData will use this plan to move between schemas. The migration plan requires an array of VersionedSchemas and an array of MigrationStages.
// eg.
MigrationStage.lightweight(
fromVersion: GoodDogSchemaV01_00_00.self,
toVersion: GoodDogSchemaV01_01_00.self
)
You use either a .lightweight MigrationStage, as you’ve already seen, or you use .custom MigrationStage for trickier changes. A custom migration stage has two handlers as closures. Before the migration, the willMigrate: closure handles examining the data objects to be migrated. After the migration, the didMigrate: closure inserts or updates the data objects based on the new schema.
MigrationStage.custom(fromVersion:toVersion:willMigrate:didMigrate:)
Suppose you decide to add a city name to the dog parks, similar to the change you made to the breed. Adding a new field to the schema is pretty straightforward, and a lightweight migration is all you’ll need.
Later on, you realize the city names should be unique so that a picker can be used. However, you have no idea how many users may have already entered the same city name on multiple dog parks. You’ll need to add logic to check and remove duplicates and insert unique city names. In that case, you’ll create a custom migration. Overall, the migration plan will move the objects from the first through the second and ultimately land on the third version of the schema.
In the following section, you’ll see how to prepare each VersionedSchema and set up the SchemaMigrationPlan.
Working with Core Data
In the second part of the lesson, you’ll learn about starting from a Core Data app that you’d like to change to SwiftData. Recall that CoreData is the precursor to SwiftData.
Converting a Core Data application to SwiftData is generally easy. There’s a Core Data project in the Starter folder. You need to recreate the models in SwiftData with the @Model macro. Internally, ManagedObjectContext is used for Core Data. It’s similar to SwiftData’s modelContext but has some differences. Next, you’ll import SwiftData and change the Core Data FetchRequests to the new @Queryor to a @fetch if you want to use FetchDescriptors. The backing object store is the same for both frameworks, though each one stores files in slightly different ways.
Continue to the demo to learn how to use migrations and the basics of converting a Core Data app to SwiftData.