使用 SwiftData 建模数据 Schema
Model your schema with SwiftData
2023年6月5日
一句话判断
SwiftData 的 Schema 建模不只是加个 @Model——@Attribute(.unique) 做冲突合并、@Relationship 管理级联删除、Schema Migration 支持版本演进,这场 Session 把数据建模的完整能力讲透了。
这场 Session 讲了什么
Session 深入讲解了 SwiftData 的 Schema 定制能力,覆盖属性宏、关系管理和 Schema 迁移三个主题。
@Model 宏的基础。给 Swift 类加上 @Model 宏,它就自动获得持久化能力——框架会根据属性定义生成完整的数据库 Schema。基础类型(String、Int、Date 等)自动支持,不需要额外的类型映射。
@Attribute 宏的定制。@Attribute(.unique) 确保属性值唯一,如果存在相同值会自动执行 upsert(更新而非插入)。@Attribute(.transient) 标记不持久化的计算属性。@Attribute(.externalStorage) 让大数据(如图片二进制)存储在外部文件中,减少数据库体积。
@Relationship 宏。一对一和一对多关系的声明式定义。支持级联删除规则(.cascade)——删除父对象时自动删除所有子对象。反向关系通过 @Relationship(inverse:) 声明,确保双向导航的一致性。
Schema 迁移。当 App 版本更新需要修改 Schema 时,SwiftData 提供了 SchemaMigrationPlan。你可以定义多个迁移阶段,每个阶段指定从哪个版本到哪个版本的转换逻辑。轻量迁移(比如添加新属性)可以自动处理,复杂迁移需要手动编写转换代码。
值得深挖的点
upsert 语义:@Attribute(.unique) 不仅仅是约束——当插入重复值时,SwiftData 会自动更新现有记录。这解决了”先查后插/更新”的常见模式,简化了同步场景的代码。
级联删除的注意事项:如果你不指定 .cascade,删除父对象时子对象仍然存在但变为”孤儿”。这个默认行为是安全的,但可能导致意料之外的数据残留。显式声明删除规则是好习惯。
迁移计划的设计:SchemaMigrationPlan 要求你定义线性版本序列。如果你跳过中间版本(比如用户从 v1 直接升级到 v3),迁移计划会依次执行 v1->v2 和 v2->v3 的转换。这保证了迁移路径的完整性。
代码片段
使用 Schema 宏定义数据模型:
import SwiftData
@Model
class Trip {
#Unique<Trip>([\.name]) // name 属性唯一
var name: String
var destination: String
var startDate: Date
var endDate: Date
// 使用 @Attribute 定制属性
@Attribute(.unique)
var tripID: String
// 一对多关系,级联删除
@Relationship(deleteRule: .cascade, inverse: \BucketListItem.trip)
var bucketListItems: [BucketListItem]
@Relationship(deleteRule: .cascade, inverse: \Accommodation.trip)
var accommodations: [Accommodation]
init(name: String, destination: String) {
self.name = name
self.destination = destination
self.startDate = Date()
self.endDate = Date()
self.tripID = UUID().uuidString
}
}
定义 Schema 迁移计划:
// 迁移计划
enum TripSchemaMigration: SchemaMigrationPlan {
// 定义 Schema 版本
static var schemas: [VersionedSchema.Type] {
[TripSchemaV1.self, TripSchemaV2.self]
}
// 定义迁移阶段
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: TripSchemaV1.self,
toVersion: TripSchemaV2.self
) { context in
// 手动迁移逻辑:比如给新属性设置默认值
}
}
最佳实践
- 为需要唯一性的属性添加
@Attribute(.unique),利用 upsert 简化同步代码 - 显式声明关系的删除规则(
.cascade或.deny),避免孤儿数据 - 大数据属性使用
@Attribute(.externalStorage)减少数据库体积 - 在 Schema 变更时尽早定义迁移计划,不要等积累太多变更再处理
- 轻量迁移(添加属性、添加索引)由框架自动处理,复杂迁移需要手写
还有什么值得关注
- SwiftData 与 Core Data 的互操作性
#Unique宏的完整语法和多属性联合唯一约束- ModelContainer 的配置选项(内存模式、多容器等)
- iCloud 同步场景下的冲突解决策略