Model your schema with SwiftData
Swift & UI 进阶 20m

使用 SwiftData 建模数据 Schema

Model your schema with SwiftData

2023年6月5日

在 Apple 官方观看视频

一句话判断

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 同步场景下的冲突解决策略
WWDC 2023