Dive deeper into SwiftData
Swift & UI 进阶 20m

深入 SwiftData:持久化配置、ModelContext 与最佳实践

Dive deeper into SwiftData

2023年6月5日

在 Apple 官方观看视频

一句话判断

SwiftData 的 ModelContainer 和 ModelConfiguration 让你精细控制持久化策略——从内存数据库到多 CloudKit 容器,一套 API 全搞定。

这场 Session 讲了什么

Nick Gillett 从 SwiftData 团队出发,深入讲解了 SwiftData 的三个核心概念:

ModelContainer。Schema 和持久化之间的桥梁。提供 Schema 后,SwiftData 自动推断完整的对象图(包括关联类型)。支持从简单到复杂的多种初始化方式,可以配置内存存储、磁盘存储、多文件存储、多 CloudKit 容器等。

ModelConfiguration。描述 Schema 的持久化细节:存储位置(内存或磁盘)、自定义文件 URL、只读模式、CloudKit 容器标识符。多个 Configuration 可以对应不同的 Model 类型组,各自独立存储和同步。

ModelContext。数据的内存视图,追踪和管理 Model 对象的状态。数据被 fetch 到 context 中,编辑以快照形式记录,直到调用 save() 才持久化。SwiftUI 的 modelContainer 修饰符自动将容器的 mainContext 注入环境,@Query 使用的就是这个 context。

Session 用 SampleTrips 示例 App 贯穿讲解,演示了标准平台实践的实现——包括撤销和 App 切换时的自动保存。

值得深挖的点

多 Schema 分离存储。SampleTrips 的旅行数据(Trip、BucketListItem、LivingAccommodations)和新添加的联系人数据(Person、Address)使用不同的 ModelConfiguration,各自的文件 URL 和 CloudKit 容器标识符互不干扰。这适合数据隔离要求严格的场景。

ModelContext 的快照机制。删除操作后,对象从 UI 消失但仍然存在于 context 中,直到 save。这意味着你可以在 save 之前实现”撤销删除”——对象的数据还在。

@Model 宏的双重角色。标注了 @Model 的类既是 Schema 描述(告诉 SwiftData 如何建表),也是代码接口(你直接操作的对象实例)。这种双重身份让 SwiftData 的使用体验非常自然。

代码片段

// 多 Schema 分离存储配置
let schema = Schema([
    Trip.self, BucketListItem.self, LivingAccommodations.self,
    Person.self, Address.self
])

// 旅行数据配置:自定义 URL + CloudKit 容器
let tripsConfig = ModelConfiguration(
    "Trips",
    schema: Schema([Trip.self, BucketListItem.self, LivingAccommodations.self]),
    url: tripsFileURL,
    cloudKitDatabase: .private("iCloud.com.example.trips")
)

// 联系人数据配置:独立的 URL + CloudKit 容器
let contactsConfig = ModelConfiguration(
    "Contacts",
    schema: Schema([Person.self, Address.self]),
    url: contactsFileURL,
    cloudKitDatabase: .private("iCloud.com.example.contacts")
)

let container = try ModelContainer(
    for: schema,
    configurations: [tripsConfig, contactsConfig]
)
// SwiftUI 中使用 modelContainer 修饰符
@main
struct SampleTripsApp: App {
    var body: some Scene {
        WindowGroup {
            TripListView()
        }
        .modelContainer(for: Trip.self)  // 自动推断关联类型
    }
}

// 在视图中访问 ModelContext
struct TripListView: View {
    @Environment(\.modelContext) private var context
    @Query(sort: \Trip.startDate) var trips: [Trip]
    
    func deleteTrip(_ trip: Trip) {
        context.delete(trip)
        // 不会立即持久化,直到 context.save() 被调用
    }
}

最佳实践

  • 先看 “Meet SwiftData” 和 “Model your Schema with SwiftData” 作为前置知识。
  • 简单场景用 modelContainer(for:) 一行搞定,复杂场景再手动配置。
  • 多个独立的对象图用不同的 ModelConfiguration 隔离存储和同步。
  • 利用 context 的快照机制实现撤销功能。
  • 测试时覆盖 Schema 迁移场景,确保 App 升级后数据不丢失。

还有什么值得关注

  • SampleTrips 是今年的示例 App,展示完整的 SwiftData 使用模式
  • @Query 属性包装器自动从 context 获取数据,支持排序和过滤
  • SwiftUI 的 modelContainer 修饰符可以在任何 View 或 Scene 上使用
  • SwiftData 自动推断 Schema 关系,但也可以手动配置关联行为
WWDC 2023