Model your schema with SwiftData
Description: Learn how to use schema macros and migration plans with SwiftData to build more complex features for your app. We'll show you how to fine-tune your persistence with @Attribute and @Relationship options. Learn how to exclude properties from your data model with @Transient and migrate from one version of your schema to the next with ease. To get the most out of this session, we recommend first watching "Meet SwiftData" and "Build an app with SwiftData" from WWDC23.
Utilizing schema macros
import SwiftData
and markclass
with@Model
to create schema- Use
@Attribute(.unique)
to make field unique, if already exists, then "upsert" will happen, updating existing data (insert -> update) - Uniqueness available for primitive value types: Numeric, String, UUID – can also decorate relationships
- Renaming variables without any attributes generates new properties and deletes old
- To keep data but rename field, specify
@Attribute(originalName: "oldName")
to map data @Attribute
also supports storing large data externally, supplying support forTransformable
s, integrate with Spotlight, modify hash- SwiftData implicitly discoveres (inverse) relationships between models for fields like
var bucketList: [BucketListItem]? = []
- The default deletion strategy for relationships when no annotations provided is "nil out"
- To delete relationships alongside, specify attribute
@Relationship(.cascade)
explicitly @Relationship
modifier also supportsoriginalName
and constraints ontoMany
for min/max count constraints, plus modifying hash- To exclude a stored property from the schema, annotate it with
@Transient
– a default value is required
Evolving schemas
- Define an enum conforming to
VersionedSchema
and put your models inside the enum (used as a namespace) - Order versions with
SchemaMigrationPlan
- Define migration stage: Lightweight (no code required), Custom (code needed)
- Provide
migrationPlan
toModelContainer
Example for VersionedSchema
:
enum SampleTripsSchemaV2: VersionedSchema {
static var models: [any PersistentModel.Type] {
[Trip.self, BucketListItem.self, LivingAccommodation.self]
}
@Model
final class Trip {
@Attribute(.unique)
var name: String
var destination: String
var start_date: Date
var end_date: Date
var bucketList: [BucketListItem]? = []
var livingAccommodation: LivingAccommodation?
}
// other models
}
Example for SchemaMigrationPlan
:
enum SampleTripsMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SampleTripsSchemaV1.self, SampleTripsSchemaV2.self, SampleTripsSchemaV3.self]
}
static var stages: [MigrationStage] { [migrateV1toV2] }
static let migrateVltoV2 = MigrationStage.custom(
fromVersion: SampleTripsSchemaV1.self,
toVersion: SampleTripsSchemaV2.self,
willMigrate: { context in
let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV1.Trip> ())
// De-duplicate Trip instances here..
try? context.save ()
},
didMigrate: nil
)
}
Example for passing migration plan to model container:
struct TripsApp: App {
let container = ModelContainer(for: Trip.self, migrationPlan: SampleTripsMigrationPlan.self)
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}