Highlight
SwiftData 的核心是 Model 的双重角色:它既是描述对象图的 Schema,又是代码中直接操作的接口。ModelContainer 负责持久化配置,ModelContext 负责追踪和管理内存中的对象状态,两者配合让数据操作变得直观且安全。
核心内容
从代码到持久化:最小化距离
以前做数据持久化,需要写模型类、写数据库操作代码、处理线程同步、管理对象生命周期。Core Data 虽然强大,但学习曲线陡峭,很多样板代码。
SwiftData 的设计目标是最小化”普通 Swift 代码”和”带持久化的 Swift 代码”之间的距离。
(01:44)SampleTrips 应用中的 Trip 类,加上 @Model 宏和少量关系注解,就获得了完整的持久化能力。SwiftData 自动推断对象图结构,不需要额外配置。
核心架构包含两个关键组件:
- ModelContainer:Schema 与持久化的桥梁,处理存储位置、版本控制、迁移、对象图隔离等
- ModelContext:数据的内存视图,对象被 fetch 到 context 中,编辑以快照形式记录,直到调用
save()才持久化
SwiftData 提供 SwiftUI 原生集成(modelContainer 修饰符)、类型安全的查询(FetchDescriptor + #Predicate 宏)、系统级 undo/autosave 支持,以及高效批量遍历(enumerate)等能力。
详细内容
ModelContainer:Schema 与持久化的桥梁
(03:54)ModelContainer 是描述数据如何在设备上存储的地方。它是 Schema 和持久化之间的桥梁,处理存储位置、版本控制、迁移、对象图隔离等。
最简单的初始化方式:
let container = ModelContainer(for: Trip.self)
关键点:
- 传入一个类型,SwiftData 自动推断完整的 Schema(包括关联类型)
Trip关联了BucketListItem和LivingAccommodation,这些类型会自动包含
ModelConfiguration:精细控制持久化
(04:53)复杂场景用 ModelConfiguration 描述持久化细节:
let schema = Schema([Trip.self, BucketListItem.self, LivingAccommodation.self, Person.self, Address.self])
let tripsConfig = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false,
cloudKitDatabase: .automatic("com.example.SampleTrips")
)
let peopleConfig = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false,
cloudKitDatabase: .automatic("com.example.SampleTrips.People")
)
let container = ModelContainer(
for: Trip.self,
configurations: tripsConfig,
peopleConfig
)
关键点:
isStoredInMemoryOnly控制是否只在内存存储cloudKitDatabase指定 CloudKit 容器标识符- 多个 Configuration 可以隔离不同的对象图,各自独立存储和同步
- 可以设置只读模式保护敏感数据
- 可以指定自定义文件 URL 或 group container
SwiftUI 集成:modelContainer 修饰符
(06:49)SwiftUI 应用使用 modelContainer 修饰符创建容器:
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Trip.self)
}
}
关键点:
modelContainer可以添加到任意 View 或 Scene- 自动将容器的
mainContext注入环境 mainContext是 MainActor 对齐的,专用于 UI 场景
ModelContext:数据的内存视图
(08:24)ModelContext 是数据的内存视图。对象被 fetch 到 context 中,编辑以快照形式记录,直到调用 save() 才持久化。
// 从环境获取 context
@Environment(\.modelContext) private var context
// 查询数据
@Query private var trips: [Trip]
// 删除操作
func deleteTrip(_ trip: Trip) {
context.delete(trip)
// 此时对象仍在 context 中,直到 save()
}
// 保存更改
try? context.save()
关键点:
context.delete()后对象仍在 context 中,只是标记为删除save()后更改才写入 ModelContainer,context 状态被清空- 对象在 context 中被引用时会保持存在,引用释放后自动清理
rollback()和reset()可以清除未保存的更改
Undo 和 Autosave
(09:58)SwiftUI 的 modelContainer 修饰符支持系统级 undo 和 autosave:
WindowGroup {
ContentView()
}
.modelContainer(for: Trip.self, isUndoEnabled: true, isAutosaveEnabled: true)
关键点:
isUndoEnabled: true绑定窗口的 undo manager 到 mainContext- 三指滑动、摇晃手势自动支持撤销/重做,无需额外代码
- Autosave 默认开启,在应用进入前后台时自动保存
- 手动创建的 context 默认关闭 autosave
FetchDescriptor:类型安全的查询
(12:02)FetchDescriptor 结合 Predicate 宏实现编译器验证的查询:
// 基础查询
let descriptor = FetchDescriptor<Trip>()
let trips = try? context.fetch(descriptor)
// 带条件的查询
let hotelDescriptor = FetchDescriptor<Trip>(
predicate: #Predicate { trip in
trip.livingAccommodation?.name == "Grand Hotel"
}
)
// 带排序和分页
let sortedDescriptor = FetchDescriptor<Trip>(
predicate: #Predicate { $0.destination == "Paris" },
sortBy: [SortDescriptor(\.startDate, order: .reverse)],
offset: 0,
limit: 20
)
关键点:
#Predicate宏使用 Swift 闭包语法,编译器验证属性和类型- SwiftData 将 Predicate 翻译成数据库查询
SortDescriptor支持按任意属性排序offset和limit控制分页
enumerate:高效批量遍历
(13:19)enumerate 封装了批量遍历的最佳实践:
try? context.enumerate(FetchDescriptor<Trip>()) { trip in
// 处理每个 trip
trip.status = .archived
}
关键点:
- 默认 batch size 为 5000,可自定义
- 自动实现批处理和 mutation guard
allowEscapingMutations: true允许遍历期间修改 context- 大数据对象(含图片、视频)建议减小 batch size 控制内存
核心启发
为多模块应用配置独立存储
- 做什么:不同功能模块的数据使用独立的 ModelConfiguration,各自存储和同步
- 为什么值得做:隔离数据减少冲突,不同模块可以有不同的同步策略
- 怎么开始:定义多个
ModelConfiguration,分别指定不同的cloudKitDatabase标识符和文件 URL
利用 enumerate 处理大数据迁移
- 做什么:批量处理历史数据(如归档旧记录、计算统计值)
- 为什么值得做:
enumerate自动管理内存,避免一次性加载所有对象 - 怎么开始:用
context.enumerate()遍历数据,调整 batch size 平衡内存和 I/O
为后台任务创建独立 Context
- 做什么:网络同步、批量处理等后台任务使用独立的 ModelContext
- 为什么值得做:避免阻塞主线程的 UI context,后台修改完成后合并到主 context
- 怎么开始:用
ModelContext(container)创建新 context,在后台 actor 中执行操作
用 Predicate 宏替代字符串查询
- 做什么:把所有 NSPredicate 字符串查询替换为
#Predicate宏 - 为什么值得做:编译器验证属性名和类型,重构时自动更新,避免运行时错误
- 怎么开始:将
NSPredicate(format: "destination == %@", "Paris")替换为#Predicate { $0.destination == "Paris" }
关联 Session
- Meet SwiftData — SwiftData 框架入门介绍
- Model your schema with SwiftData — Schema 宏和迁移计划详解
- Build an app with SwiftData — 用 SwiftData 构建完整应用的实战演示
评论
GitHub Issues · utterances