【Swift Data】Apple 新一代数据持久化框架——用 Swift 宏与类型安全重塑本地存储
iOS三方库精读 · 第 21 期
一、一句话介绍
Swift Data 是 Apple 在 WWDC 2023 推出的全新数据持久化框架,基于 Swift 宏(Macro)和类型安全设计,让 iOS/macOS 应用中的本地数据存储从"写样板代码"变为"声明即完成"。
| 属性 | 信息 |
|---|---|
| 发布时间 | WWDC 2023(iOS 17 / macOS 14) |
| 维护者 | Apple(系统框架) |
| License | Apple Platform SDK |
| 支持平台 | iOS 17+ / macOS 14+ / tvOS 17+ / watchOS 10+ |
| 语言 | Swift(纯 Swift API) |
二、为什么选择它
没有 Swift Data 时的痛点
| 痛点 | Core Data 做法 | Swift Data 解决方案 |
|---|---|---|
| 需要 .xcdatamodeld 文件 | 可视化编辑器 + 代码生成 | @Model 宏一行搞定 |
| NSManagedObject 类型不安全 | @NSManaged + 强转 | 原生 Swift 类型,编译期检查 |
| Context 手动管理 | NSManagedObjectContext + perform {} | @ModelActor + async/await |
| 查询语法晦涩 | NSPredicate(format:) 字符串 | #Predicate 宏,类型安全 |
| SwiftUI 集成笨拙 | @FetchRequest + NSManagedObject | @Query + 普通 Swift 类 |
| iCloud 同步配置复杂 | NSPersistentCloudKitContainer | ModelConfiguration 一行启用 |
Swift Data 的核心优势:
- Swift 原生:使用
@Model宏定义数据模型,无需继承 NSManagedObject - 类型安全查询:
#Predicate宏在编译期检查查询条件的类型正确性 - SwiftUI 深度集成:
@Query属性包装器自动监听数据变化并刷新视图 - 现代并发:
@ModelActor天然支持 Swift Concurrency - 自动迁移:
VersionedSchema+SchemaMigrationPlan声明式管理模型版本 - 底层复用:底层仍基于 Core Data + SQLite,性能与稳定性经过十多年验证
三、核心功能速览
基础层(新手必读)
环境集成
Swift Data 是系统框架,无需添加任何依赖:
import SwiftData
定义数据模型
@Model
class TodoItem {
var title: String
var createdAt: Date
var isDone: Bool
var priority: Int
init(title: String, priority: Int = 1) {
self.title = title
self.createdAt = Date()
self.isDone = false
self.priority = priority
}
}
@Model 宏会自动为类生成:
PersistentModel协议一致性- 属性的持久化存储映射
- 变更追踪能力
Hashable、Identifiable一致性
配置 ModelContainer
// 方式一:SwiftUI modifier(推荐)
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: TodoItem.self)
}
}
// 方式二:自定义配置
let config = ModelConfiguration(
isStoredInMemoryOnly: false,
allowsSave: true
)
let container = try ModelContainer(
for: TodoItem.self,
configurations: config
)
CRUD 操作
// 获取 context(在 SwiftUI View 中)
@Environment(\.modelContext) private var context
// Create
let item = TodoItem(title: "学习 Swift Data")
context.insert(item)
// Read(使用 @Query)
@Query(sort: \TodoItem.createdAt, order: .reverse)
var items: [TodoItem]
// Update(直接修改属性,自动追踪)
item.isDone = true
// Delete
context.delete(item)
// 手动保存(通常自动保存)
try context.save()
进阶层(实战必备)
@Query 的强大查询能力
// 带过滤和排序
@Query(
filter: #Predicate<TodoItem> { !$0.isDone },
sort: [SortDescriptor(\TodoItem.priority, order: .reverse)],
animation: .default
)
var pendingItems: [TodoItem]
// 动态查询
@Query var items: [TodoItem]
init(searchText: String) {
let predicate = #Predicate<TodoItem> {
searchText.isEmpty || $0.title.contains(searchText)
}
_items = Query(filter: predicate)
}
关系定义
@Model
class User {
var name: String
@Relationship(deleteRule: .cascade, inverse: \Post.author)
var posts: [Post] = []
init(name: String) { self.name = name }
}
@Model
class Post {
var title: String
var content: String
var author: User?
init(title: String, content: String) {
self.title = title
self.content = content
}
}
@ModelActor 并发安全
@ModelActor
actor DataManager {
func importItems(_ data: [ImportData]) throws {
for d in data {
let item = TodoItem(title: d.title, priority: d.priority)
modelContext.insert(item)
}
try modelContext.save()
}
}
// 使用
let manager = DataManager(
modelContainer: container
)
try await manager.importItems(data)
深入层(源码视角)
核心模块职责
| 类/协议 | 职责 |
|---|---|
@Model 宏 | 编译期生成 PersistentModel 一致性、属性映射、变更追踪 |
ModelContainer | 数据存储容器,管理 Schema + Configuration + 底层 Store |
ModelContext | 对象图工作区,追踪 insert/update/delete 变更 |
PersistentModel | 核心协议,定义持久化模型的能力 |
#Predicate 宏 | 编译期将 Swift 闭包转换为类型安全的查询表达式 |
@Query | SwiftUI 属性包装器,自动执行查询并驱动视图刷新 |
FetchDescriptor | 查询描述,支持 predicate + sort + limit + offset |
VersionedSchema | 版本化数据模型,配合 SchemaMigrationPlan 管理迁移 |
@Model 宏展开分析
// 你写的:
@Model class TodoItem {
var title: String
}
// 宏展开后(简化版):
class TodoItem: PersistentModel {
// 属性存储改为 backing storage
@_PersistedProperty var title: String
// 自动生成的 schema
static var schemaMetadata: [Schema.PropertyMetadata] {
[.init(name: "title", keypath: \TodoItem.title)]
}
// 变更追踪
// get/set 拦截器自动注入
}
Swift Data 与 Core Data 的关系
┌────────────────────────────┐
│ Swift Data API │ ← 你写的代码
│ @Model / @Query / #Predicate │
├────────────────────────────┤
│ 自动桥接层(宏生成) │ ← 编译器生成
│ PersistentModel → NSManagedObject │
├────────────────────────────┤
│ Core Data 引擎 │ ← 系统框架
│ NSPersistentContainer / NSManagedObjectContext │
├────────────────────────────┤
│ SQLite │ ← 底层存储
└────────────────────────────┘
Swift Data 并非重新造轮子,而是在 Core Data 之上用 Swift 宏构建了一层类型安全的现代 API。底层的持久化、查询优化、并发控制仍由 Core Data 引擎驱动。
四、实战 Demo
以下 Demo 已在项目中以可交互 SwiftUI 页面实现,见
SwiftDataDetail/文件夹。
Demo 1:基础 CRUD
@Model
class SDDemoItem {
var title: String
var createdAt: Date
var isDone: Bool
var priority: Int
init(title: String, priority: Int = 1) {
self.title = title
self.createdAt = Date()
self.isDone = false
self.priority = priority
}
}
// 创建
let item = SDDemoItem(title: "学习 Swift Data", priority: 2)
context.insert(item)
// 查询
let descriptor = FetchDescriptor<SDDemoItem>(
sortBy: [SortDescriptor(\.priority, order: .reverse)]
)
let items = try context.fetch(descriptor)
// 更新(直接修改属性)
item.isDone = true
// 删除
context.delete(item)
Demo 2:#Predicate 动态过滤
func filteredItems(keyword: String, doneOnly: Bool) throws -> [SDDemoItem] {
let predicate = #Predicate<SDDemoItem> { item in
(keyword.isEmpty || item.title.contains(keyword)) &&
(!doneOnly || item.isDone)
}
let descriptor = FetchDescriptor(
predicate: predicate,
sortBy: [SortDescriptor(\.createdAt, order: .reverse)]
)
return try context.fetch(descriptor)
}
Demo 3:一对多关系
@Model class SDUser {
var name: String
@Relationship(deleteRule: .cascade)
var posts: [SDPost] = []
init(name: String) { self.name = name }
}
@Model class SDPost {
var title: String
var createdAt: Date
var author: SDUser?
init(title: String) {
self.title = title
self.createdAt = Date()
}
}
// 创建用户并添加文章
let user = SDUser(name: "张三")
context.insert(user)
let post = SDPost(title: "第一篇文章")
post.author = user
context.insert(post)
五、源码亮点
进阶层:值得借鉴的用法
1. @Model 宏的声明式设计
// Core Data 需要:
// 1. 创建 .xcdatamodeld 文件
// 2. 定义 Entity + Attributes
// 3. 生成 NSManagedObject 子类
// 4. 使用 @NSManaged 标注属性
// Swift Data 只需:
@Model class Item {
var title: String // 自动持久化
@Transient var temp: String = "" // 排除持久化
@Attribute(.unique) var id: UUID // 唯一约束
}
2. #Predicate 的编译期安全
// Core Data 的 NSPredicate 是字符串,运行时才报错:
// NSPredicate(format: "titl CONTAINS %@", keyword) // typo → 运行时崩溃
// Swift Data 的 #Predicate 是编译期检查:
// #Predicate<Item> { $0.titl.contains(keyword) } // typo → 编译报错 ✅
3. @Query 的响应式绑定
// @Query 不只是查询,还自动监听数据变化:
// 当任何 Item 被 insert/update/delete 时,
// @Query 会自动重新执行查询并触发视图刷新。
// 无需手动 NotificationCenter、无需 NSFetchedResultsController。
深入层:设计思想解析
1. 宏驱动的元编程
Swift Data 是 Swift 宏的旗舰级应用案例。@Model 宏在编译期:
- 分析类的存储属性
- 生成 PersistentModel 协议的所有必要实现
- 注入属性的 get/set 拦截器实现变更追踪
- 创建 Schema 元数据用于 ModelContainer 初始化
这种设计消除了运行时反射的性能开销,同时保持了类型安全。
2. 与 SwiftUI 的 Observation 一致性
Swift Data 的 @Model 与 SwiftUI 的 @Observable 共享相同的属性观察机制。当 SwiftUI 视图读取 @Model 对象的属性时,会自动建立依赖追踪,属性变更时精确刷新对应视图——而非整个视图树。
3. 渐进式迁移策略
enum MySchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] { [Item.self] }
@Model class Item {
var title: String
}
}
enum MySchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] { [Item.self] }
@Model class Item {
var title: String
var priority: Int = 0 // 新增字段
}
}
enum MyMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[MySchemaV1.self, MySchemaV2.self]
}
static var stages: [MigrationStage] {
[.lightweight(fromVersion: MySchemaV1.self, toVersion: MySchemaV2.self)]
}
}
六、踩坑记录
🕳️ 坑 1:@Model 类必须是 class 而非 struct
// ❌ 编译报错
@Model struct Item { ... }
// ✅ 必须使用 class
@Model class Item { ... }
原因:Swift Data 需要引用语义来实现变更追踪和对象图管理。
🕳️ 坑 2:@Query 的 filter 参数只能使用静态表达式
// ❌ 运行时崩溃
@Query(filter: #Predicate<Item> { $0.title.contains(someVariable) })
var items: [Item]
// ✅ 在 init 中动态构建
init(searchText: String) {
_items = Query(filter: #Predicate<Item> {
searchText.isEmpty || $0.title.localizedStandardContains(searchText)
})
}
🕳️ 坑 3:#Predicate 不支持所有 Swift 表达式
// ❌ 不支持 .map / .compactMap 等高阶函数
#Predicate<User> { $0.posts.map(\.title).contains("Swift") }
// ✅ 使用支持的操作
#Predicate<User> { user in
user.posts.contains { $0.title == "Swift" }
}
🕳️ 坑 4:ModelContainer 线程安全
// ❌ 在后台线程使用主线程的 modelContext
Task.detached {
let ctx = container.mainContext // 主线程 context,后台使用不安全
ctx.insert(item)
}
// ✅ 使用 @ModelActor
@ModelActor actor BackgroundWorker {
func doWork() { /* 自动获得独立 context */ }
}
🕳️ 坑 5:@Transient 属性必须有默认值
// ❌ 编译报错
@Model class Item {
@Transient var cachedResult: String // 缺少默认值
}
// ✅ 提供默认值
@Model class Item {
@Transient var cachedResult: String = ""
}
七、横向对比
Swift Data vs Core Data vs GRDB
| 维度 | Swift Data | Core Data | GRDB |
|---|---|---|---|
| 最低版本 | iOS 17.0 | iOS 3.0 | iOS 13.0 |
| API 风格 | Swift 原生 | ObjC 风格 | SQL + Swift |
| 模型定义 | @Model 宏 | .xcdatamodeld | Codable + SQL |
| 类型安全 | 编译期检查 | 需强转 | Codable |
| 查询语法 | #Predicate 宏 | NSPredicate(format:) | 原生 SQL |
| SwiftUI 集成 | @Query 原生 | @FetchRequest | 自建 Observation |
| 并发处理 | @ModelActor | perform {} | DatabasePool |
| iCloud 同步 | 内置支持 | CloudKit Container | 无 |
| 迁移策略 | VersionedSchema | Lightweight Migration | DatabaseMigrator |
| 学习曲线 | 平缓 | 陡峭 | 中等 |
| SQL 控制 | 无 | 无 | 完整 SQL |
| 包体积 | 系统框架(0 KB) | 系统框架(0 KB) | ~500 KB |
推荐场景
选 Swift Data:
- 新项目且最低支持 iOS 17+
- 纯 SwiftUI 应用,追求最简 API
- 需要 iCloud 同步且不想处理 CloudKit 复杂度
- 团队熟悉 Swift 但不熟悉 Core Data
不选 Swift Data:
- 需要兼容 iOS 16 及以下(选 Core Data)
- 需要复杂 SQL 查询和 JOIN(选 GRDB)
- 已有大量 Core Data 存量代码(继续维护 Core Data)
- 需要精细控制数据库行为(选 GRDB)
当前状态
Swift Data 正处于快速迭代期,Apple 在 WWDC 2024 / 2025 持续增强其功能(如 #Expression 宏、更多关系类型支持等)。底层仍基于 Core Data 引擎,两者长期共存。
八、参考资源
官方资源
- Swift Data | Apple Developer
- WWDC 2023: Meet SwiftData
- WWDC 2023: Build an app with SwiftData
- WWDC 2023: Model your schema with SwiftData
- WWDC 2024: What's new in SwiftData
社区精选
- Hacking with Swift - SwiftData by Example
- SwiftData 从入门到精通 - objc.io
- Donny Wals - Practical SwiftData
📝 互动模块
小作业
用 Swift Data 创建一个包含 User 和 Post 两个实体的数据模型:
User包含name: String属性Post包含title: String、createdAt: Date属性- 实现一对多关系:一个 User 可以有多个 Post(使用
@Relationship(deleteRule: .cascade)) - 实现完整 CRUD:创建 User → 添加 Post → 查询 → 删除
思考题
Swift Data 的 @Query 属性包装器在底层是如何实现"数据变化自动刷新视图"的?如果查询涉及大量数据(10 万条),你会如何优化查询性能?
提示:思考 FetchDescriptor 的
fetchLimit和fetchOffset,以及@Query与 SwiftUI Observation 框架的协作机制。
读者征集
你在项目中使用 Swift Data 遇到过哪些"意想不到"的坑?特别是从 Core Data 迁移到 Swift Data 的经验,欢迎分享!