【Swift Data】Apple 新一代数据持久化框架——用 Swift 宏与类型安全重塑本地存储

3 阅读7分钟

【Swift Data】Apple 新一代数据持久化框架——用 Swift 宏与类型安全重塑本地存储

iOS三方库精读 · 第 21 期


一、一句话介绍

Swift Data 是 Apple 在 WWDC 2023 推出的全新数据持久化框架,基于 Swift 宏(Macro)和类型安全设计,让 iOS/macOS 应用中的本地数据存储从"写样板代码"变为"声明即完成"。

属性信息
发布时间WWDC 2023(iOS 17 / macOS 14)
维护者Apple(系统框架)
LicenseApple 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 同步配置复杂NSPersistentCloudKitContainerModelConfiguration 一行启用

Swift Data 的核心优势:

  1. Swift 原生:使用 @Model 宏定义数据模型,无需继承 NSManagedObject
  2. 类型安全查询#Predicate 宏在编译期检查查询条件的类型正确性
  3. SwiftUI 深度集成@Query 属性包装器自动监听数据变化并刷新视图
  4. 现代并发@ModelActor 天然支持 Swift Concurrency
  5. 自动迁移VersionedSchema + SchemaMigrationPlan 声明式管理模型版本
  6. 底层复用:底层仍基于 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 协议一致性
  • 属性的持久化存储映射
  • 变更追踪能力
  • HashableIdentifiable 一致性
配置 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 闭包转换为类型安全的查询表达式
@QuerySwiftUI 属性包装器,自动执行查询并驱动视图刷新
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 DataCore DataGRDB
最低版本iOS 17.0iOS 3.0iOS 13.0
API 风格Swift 原生ObjC 风格SQL + Swift
模型定义@Model.xcdatamodeldCodable + SQL
类型安全编译期检查需强转Codable
查询语法#PredicateNSPredicate(format:)原生 SQL
SwiftUI 集成@Query 原生@FetchRequest自建 Observation
并发处理@ModelActorperform {}DatabasePool
iCloud 同步内置支持CloudKit Container
迁移策略VersionedSchemaLightweight MigrationDatabaseMigrator
学习曲线平缓陡峭中等
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 创建一个包含 UserPost 两个实体的数据模型:

  • User 包含 name: String 属性
  • Post 包含 title: StringcreatedAt: Date 属性
  • 实现一对多关系:一个 User 可以有多个 Post(使用 @Relationship(deleteRule: .cascade)
  • 实现完整 CRUD:创建 User → 添加 Post → 查询 → 删除

思考题

Swift Data 的 @Query 属性包装器在底层是如何实现"数据变化自动刷新视图"的?如果查询涉及大量数据(10 万条),你会如何优化查询性能?

提示:思考 FetchDescriptor 的 fetchLimitfetchOffset,以及 @Query 与 SwiftUI Observation 框架的协作机制。

读者征集

你在项目中使用 Swift Data 遇到过哪些"意想不到"的坑?特别是从 Core Data 迁移到 Swift Data 的经验,欢迎分享!