告别 Core Data 的复杂配置,拥抱纯 Swift 的声明式持久化体验。iOS 17+ 推出的全新框架。
1. SwiftData 简介
SwiftData 是 Apple 在 WWDC23 推出的现代化数据持久化框架,它基于 Core Data 构建,但提供了更简洁的 Swift 原生 API。
核心优势
- 声明式定义:使用
@Model宏定义数据模型 - 自动同步:与 SwiftUI 深度集成,数据变化自动刷新 UI
- 类型安全:使用
#Predicate进行编译时检查的查询 - 零配置:无需手动管理 Schema 迁移
2. @Model 宏
基础定义
import SwiftData
@Model
final class TodoItem {
var title: String
var timestamp: Date
var isDone: Bool
var priority: Int
init(title: String, timestamp: Date = .now, isDone: Bool = false, priority: Int = 0) {
self.title = title
self.timestamp = timestamp
self.isDone = isDone
self.priority = priority
}
}
属性修饰符
@Model
final class User {
// 唯一约束
@Attribute(.unique) var email: String
var name: String
// 不持久化的属性
@Transient var temporaryData: String = ""
// 自定义属性名
@Attribute(originalName: "user_avatar") var avatar: Data?
// 加密存储
@Attribute(.encrypt) var sensitiveInfo: String?
init(email: String, name: String) {
self.email = email
self.name = name
}
}
关系定义
@Model
final class Author {
var name: String
// 一对多关系
@Relationship(deleteRule: .cascade, inverse: \Book.author)
var books: [Book] = []
init(name: String) {
self.name = name
}
}
@Model
final class Book {
var title: String
var author: Author?
init(title: String, author: Author? = nil) {
self.title = title
self.author = author
}
}
3. 配置与初始化
基础配置
import SwiftUI
import SwiftData
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [TodoItem.self, User.self])
}
}
自定义配置
@main
struct MyApp: App {
let container: ModelContainer
init() {
let schema = Schema([TodoItem.self, User.self])
let config = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false, // 是否仅内存存储
allowsSave: true, // 是否允许保存
groupContainer: .automatic // App Group 支持
)
do {
container = try ModelContainer(for: schema, configurations: config)
} catch {
fatalError("无法创建 ModelContainer: \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
多配置场景
// 主数据库 + 缓存数据库
let mainConfig = ModelConfiguration(
"Main",
schema: Schema([User.self, Post.self])
)
let cacheConfig = ModelConfiguration(
"Cache",
schema: Schema([CachedImage.self]),
isStoredInMemoryOnly: true
)
let container = try ModelContainer(
for: Schema([User.self, Post.self, CachedImage.self]),
configurations: mainConfig, cacheConfig
)
4. CRUD 操作
创建 (Create)
struct AddTodoView: View {
@Environment(\.modelContext) private var context
@State private var title = ""
var body: some View {
Form {
TextField("标题", text: $title)
Button("添加") {
let todo = TodoItem(title: title)
context.insert(todo)
// 自动保存,无需手动调用 save()
}
}
}
}
读取 (Read)
struct TodoListView: View {
// 自动查询并监听变化
@Query var todos: [TodoItem]
// 带排序
@Query(sort: \TodoItem.timestamp, order: .reverse)
var sortedTodos: [TodoItem]
// 带过滤
@Query(filter: #Predicate<TodoItem> { !$0.isDone })
var pendingTodos: [TodoItem]
// 复杂查询
@Query(
filter: #Predicate<TodoItem> { $0.priority > 5 && !$0.isDone },
sort: [SortDescriptor(\TodoItem.priority, order: .reverse)],
animation: .default
)
var urgentTodos: [TodoItem]
var body: some View {
List(todos) { todo in
TodoRow(todo: todo)
}
}
}
动态查询
struct SearchableListView: View {
@State private var searchText = ""
var body: some View {
TodoListContent(searchText: searchText)
.searchable(text: $searchText)
}
}
struct TodoListContent: View {
@Query var todos: [TodoItem]
init(searchText: String) {
let predicate = #Predicate<TodoItem> { todo in
searchText.isEmpty || todo.title.contains(searchText)
}
_todos = Query(filter: predicate, sort: \TodoItem.timestamp)
}
var body: some View {
List(todos) { todo in
Text(todo.title)
}
}
}
更新 (Update)
struct TodoRow: View {
@Bindable var todo: TodoItem // iOS 17+ @Bindable
var body: some View {
HStack {
TextField("标题", text: $todo.title)
Toggle("完成", isOn: $todo.isDone)
}
// 修改自动保存
}
}
删除 (Delete)
struct TodoListView: View {
@Environment(\.modelContext) private var context
@Query var todos: [TodoItem]
var body: some View {
List {
ForEach(todos) { todo in
TodoRow(todo: todo)
}
.onDelete(perform: deleteTodos)
}
}
private func deleteTodos(at offsets: IndexSet) {
for index in offsets {
context.delete(todos[index])
}
}
}
5. #Predicate 查询
基础用法
// 简单条件
let completed = #Predicate<TodoItem> { $0.isDone }
// 多条件
let urgent = #Predicate<TodoItem> { item in
item.priority > 5 && !item.isDone
}
// 字符串匹配
let search = #Predicate<TodoItem> { item in
item.title.localizedStandardContains("Swift")
}
// 日期比较
let today = Date()
let recentItems = #Predicate<TodoItem> { item in
item.timestamp > today.addingTimeInterval(-86400)
}
动态 Predicate
func createPredicate(searchText: String, showCompleted: Bool) -> Predicate<TodoItem> {
return #Predicate<TodoItem> { item in
(searchText.isEmpty || item.title.contains(searchText)) &&
(showCompleted || !item.isDone)
}
}
6. 后台操作
ModelActor
@ModelActor
actor DataManager {
func importData(_ items: [ImportItem]) throws {
for item in items {
let todo = TodoItem(title: item.title)
modelContext.insert(todo)
}
try modelContext.save()
}
func fetchCount() -> Int {
let descriptor = FetchDescriptor<TodoItem>()
return (try? modelContext.fetchCount(descriptor)) ?? 0
}
}
// 使用
struct ContentView: View {
@Environment(\.modelContext) private var context
var body: some View {
Button("导入数据") {
Task {
let manager = DataManager(modelContainer: context.container)
try await manager.importData(importItems)
}
}
}
}
7. 数据迁移
轻量迁移
SwiftData 支持自动轻量迁移:
- 添加新属性(需提供默认值)
- 删除属性
- 重命名属性(使用
originalName)
@Model
final class TodoItem {
var title: String
var isDone: Bool
// 新增属性,提供默认值
var createdAt: Date = .now
// 重命名属性
@Attribute(originalName: "done") var isCompleted: Bool
}
自定义迁移
enum TodoSchemaV1: VersionedSchema {
static var models: [any PersistentModel.Type] { [TodoItem.self] }
static var versionIdentifier = Schema.Version(1, 0, 0)
@Model
final class TodoItem {
var title: String
var isDone: Bool
}
}
enum TodoSchemaV2: VersionedSchema {
static var models: [any PersistentModel.Type] { [TodoItem.self] }
static var versionIdentifier = Schema.Version(2, 0, 0)
@Model
final class TodoItem {
var title: String
var isDone: Bool
var priority: Int = 0 // 新增
}
}
enum TodoMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[TodoSchemaV1.self, TodoSchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: TodoSchemaV1.self,
toVersion: TodoSchemaV2.self
)
}
8. 与 Core Data 对比
| 特性 | Core Data | SwiftData |
|---|---|---|
| 模型定义 | .xcdatamodeld 文件 | Swift 代码 + @Model |
| 上下文管理 | 手动 NSManagedObjectContext | 自动 @Environment |
| 查询语法 | NSPredicate 字符串 | #Predicate 类型安全 |
| SwiftUI 集成 | @FetchRequest | @Query |
| 学习曲线 | 陡峭 | 平缓 |
| 最低版本 | iOS 3+ | iOS 17+ |
9. 最佳实践
✅ 推荐
// 1. 使用 @Query 自动监听数据变化
@Query var items: [Item]
// 2. 在 @Model 类中提供完整的初始化器
@Model
final class Item {
var name: String
var createdAt: Date
init(name: String, createdAt: Date = .now) {
self.name = name
self.createdAt = createdAt
}
}
// 3. 使用 @ModelActor 进行后台操作
@ModelActor
actor BackgroundProcessor { }
❌ 避免
// 1. 不要在主线程进行大量数据操作
for item in hugeArray {
context.insert(Item(name: item)) // ❌ 阻塞 UI
}
// 2. 不要忘记处理可选关系
@Model class Book {
var author: Author // ❌ 应该是可选的
}
// 3. 不要在 @Transient 属性上依赖持久化
@Transient var cache: [String] = [] // ❌ 重启后丢失