WWDC Quick Look 💓 By SwiftGGTeam
Dive deeper into SwiftData

Dive deeper into SwiftData

观看原视频

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 关联了 BucketListItemLivingAccommodation,这些类型会自动包含

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:02FetchDescriptor 结合 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 支持按任意属性排序
  • offsetlimit 控制分页

enumerate:高效批量遍历

13:19enumerate 封装了批量遍历的最佳实践:

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

评论

GitHub Issues · utterances