swiftdata
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftData
SwiftData
Persist, query, and manage structured data in iOS 26+ apps using SwiftData
with Swift 6.2.
在iOS 26+应用中结合Swift 6.2使用SwiftData,实现结构化数据的持久化、查询与管理。
Model Definition
模型定义
Apply to a class (not struct). Generates , , .
@ModelPersistentModelObservableSendableswift
@Model
class Trip {
var name: String
var destination: String
var startDate: Date
var endDate: Date
var isFavorite: Bool = false
@Attribute(.externalStorage) var imageData: Data?
@Relationship(deleteRule: .cascade, inverse: \LivingAccommodation.trip)
var accommodation: LivingAccommodation?
@Transient var isSelected: Bool = false // Always provide default
init(name: String, destination: String, startDate: Date, endDate: Date) {
self.name = name; self.destination = destination
self.startDate = startDate; self.endDate = endDate
}
}@Attribute options: , , , , (iOS 18+), , . Rename: .
.externalStorage.unique.spotlight.allowsCloudEncryption.preserveValueOnDeletion.ephemeral.transformable(by:)@Attribute(originalName: "old_name")@Relationship: /(default)//. Specify for reliable behavior. Unidirectional (iOS 18+): .
deleteRule:.cascade.nullify.deny.noActioninverse:inverse: nil#Unique (iOS 18+): -- compound uniqueness.
#Unique<Person>([\.firstName, \.lastName])#Index (iOS 18+): -- query indexes.
#Index<Trip>([\.name], [\.startDate, \.endDate])Inheritance (iOS 26+): .
@Model class BusinessTrip: Trip { var company: String }Supported types: , / variants, , , , , , , , , , , , enums, structs (composite, iOS 18+), relationships to classes.
BoolIntUIntFloatDoubleStringDateDataURLUUIDDecimalArrayDictionarySetCodableCodable@Model将应用到类(而非结构体)上,会自动生成、、协议实现。
@ModelPersistentModelObservableSendableswift
@Model
class Trip {
var name: String
var destination: String
var startDate: Date
var endDate: Date
var isFavorite: Bool = false
@Attribute(.externalStorage) var imageData: Data?
@Relationship(deleteRule: .cascade, inverse: \LivingAccommodation.trip)
var accommodation: LivingAccommodation?
@Transient var isSelected: Bool = false // 必须提供默认值
init(name: String, destination: String, startDate: Date, endDate: Date) {
self.name = name; self.destination = destination
self.startDate = startDate; self.endDate = endDate
}
}@Attribute可选配置:、、、、(iOS 18+)、、。重命名用法:。
.externalStorage.unique.spotlight.allowsCloudEncryption.preserveValueOnDeletion.ephemeral.transformable(by:)@Attribute(originalName: "old_name")@Relationship配置:可选/(默认)//。建议指定保证关联行为稳定。单向关联(iOS 18+):。
deleteRule:.cascade.nullify.deny.noActioninverse:inverse: nil#Unique (iOS 18+): -- 复合唯一性约束。
#Unique<Person>([\.firstName, \.lastName])#Index (iOS 18+): -- 优化查询速度的索引。
#Index<Trip>([\.name], [\.startDate, \.endDate])继承 (iOS 26+):。
@Model class BusinessTrip: Trip { var company: String }支持的类型:、/各类变体、、、、、、、、、、、、遵循的枚举、遵循的结构体(复合类型,iOS 18+)、指向其他类的关联关系。
BoolIntUIntFloatDoubleStringDateDataURLUUIDDecimalArrayDictionarySetCodableCodable@ModelModelContainer Setup
ModelContainer配置
swift
// Basic
let container = try ModelContainer(for: Trip.self, LivingAccommodation.self)
// Configured
let config = ModelConfiguration("Store", isStoredInMemoryOnly: false,
groupContainer: .identifier("group.com.example.app"),
cloudKitDatabase: .private("iCloud.com.example.app"))
let container = try ModelContainer(for: Trip.self, configurations: config)
// With migration plan
let container = try ModelContainer(for: SchemaV2.Trip.self,
migrationPlan: TripMigrationPlan.self)
// In-memory (previews/tests)
let container = try ModelContainer(for: Trip.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true))swift
// 基础用法
let container = try ModelContainer(for: Trip.self, LivingAccommodation.self)
// 自定义配置
let config = ModelConfiguration("Store", isStoredInMemoryOnly: false,
groupContainer: .identifier("group.com.example.app"),
cloudKitDatabase: .private("iCloud.com.example.app"))
let container = try ModelContainer(for: Trip.self, configurations: config)
// 搭配迁移计划使用
let container = try ModelContainer(for: SchemaV2.Trip.self,
migrationPlan: TripMigrationPlan.self)
// 内存存储(预览/测试用)
let container = try ModelContainer(for: Trip.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true))CRUD Operations
CRUD操作
swift
// CREATE
let trip = Trip(name: "Summer", destination: "Paris", startDate: .now, endDate: .now + 86400*7)
modelContext.insert(trip)
try modelContext.save() // or rely on autosave
// READ
let trips = try modelContext.fetch(FetchDescriptor<Trip>(
predicate: #Predicate { $0.destination == "Paris" },
sortBy: [SortDescriptor(\.startDate)]))
// UPDATE -- modify properties directly; autosave handles persistence
trip.destination = "Rome"
// DELETE
modelContext.delete(trip)
try modelContext.delete(model: Trip.self, where: #Predicate { $0.isFavorite == false })
// TRANSACTION (atomic)
try modelContext.transaction {
modelContext.insert(trip); trip.isFavorite = true
}swift
// 创建
let trip = Trip(name: "Summer", destination: "Paris", startDate: .now, endDate: .now + 86400*7)
modelContext.insert(trip)
try modelContext.save() // 也可以依赖自动保存机制
// 查询
let trips = try modelContext.fetch(FetchDescriptor<Trip>(
predicate: #Predicate { $0.destination == "Paris" },
sortBy: [SortDescriptor(\.startDate)]))
// 更新 -- 直接修改属性即可,自动保存会处理持久化逻辑
trip.destination = "Rome"
// 删除
modelContext.delete(trip)
try modelContext.delete(model: Trip.self, where: #Predicate { $0.isFavorite == false })
// 事务(原子操作)
try modelContext.transaction {
modelContext.insert(trip); trip.isFavorite = true
}@Query in SwiftUI
SwiftUI中的@Query用法
swift
struct TripListView: View {
@Query(filter: #Predicate<Trip> { $0.isFavorite == true },
sort: \.startDate, order: .reverse)
private var favorites: [Trip]
var body: some View { List(favorites) { trip in Text(trip.name) } }
}
// Dynamic query via init
struct SearchView: View {
@Query private var trips: [Trip]
init(search: String) {
_trips = Query(filter: #Predicate<Trip> { trip in
search.isEmpty || trip.name.localizedStandardContains(search)
}, sort: [SortDescriptor(\.name)])
}
var body: some View { List(trips) { trip in Text(trip.name) } }
}
// FetchDescriptor query
struct RecentView: View {
static var desc: FetchDescriptor<Trip> {
var d = FetchDescriptor<Trip>(sortBy: [SortDescriptor(\.startDate)])
d.fetchLimit = 5; return d
}
@Query(RecentView.desc) private var recent: [Trip]
var body: some View { List(recent) { trip in Text(trip.name) } }
}swift
struct TripListView: View {
@Query(filter: #Predicate<Trip> { $0.isFavorite == true },
sort: \.startDate, order: .reverse)
private var favorites: [Trip]
var body: some View { List(favorites) { trip in Text(trip.name) } }
}
// 通过构造器实现动态查询
struct SearchView: View {
@Query private var trips: [Trip]
init(search: String) {
_trips = Query(filter: #Predicate<Trip> { trip in
search.isEmpty || trip.name.localizedStandardContains(search)
}, sort: [SortDescriptor(\.name)])
}
var body: some View { List(trips) { trip in Text(trip.name) } }
}
// 基于FetchDescriptor的查询
struct RecentView: View {
static var desc: FetchDescriptor<Trip> {
var d = FetchDescriptor<Trip>(sortBy: [SortDescriptor(\.startDate)])
d.fetchLimit = 5; return d
}
@Query(RecentView.desc) private var recent: [Trip]
var body: some View { List(recent) { trip in Text(trip.name) } }
}#Predicate
#Predicate用法
swift
#Predicate<Trip> { $0.destination.localizedStandardContains("paris") } // String
#Predicate<Trip> { $0.startDate > Date.now } // Date
#Predicate<Trip> { $0.isFavorite && $0.destination != "Unknown" } // Compound
#Predicate<Trip> { $0.accommodation?.name != nil } // Optional
#Predicate<Trip> { $0.tags.contains { $0.name == "adventure" } } // CollectionSupported: , , , , , , , , , , , , , , , arithmetic, ternary, optional chaining, nil coalescing, type casting. Not supported: flow control, nested declarations, arbitrary method calls.
==!=<<=>>=&&||!contains()allSatisfy()filter()starts(with:)localizedStandardContains()caseInsensitiveCompare()swift
#Predicate<Trip> { $0.destination.localizedStandardContains("paris") } // 字符串匹配
#Predicate<Trip> { $0.startDate > Date.now } // 日期比较
#Predicate<Trip> { $0.isFavorite && $0.destination != "Unknown" } // 复合条件
#Predicate<Trip> { $0.accommodation?.name != nil } // 可选值判断
#Predicate<Trip> { $0.tags.contains { $0.name == "adventure" } } // 集合判断支持的操作:、、、、、、、、、、、、、、、算术运算、三元运算符、可选链、空值合并、类型转换。不支持:流程控制语句、嵌套声明、任意自定义方法调用。
==!=<<=>>=&&||!contains()allSatisfy()filter()starts(with:)localizedStandardContains()caseInsensitiveCompare()FetchDescriptor
FetchDescriptor用法
swift
var d = FetchDescriptor<Trip>(predicate: ..., sortBy: [...])
d.fetchLimit = 20; d.fetchOffset = 0
d.includePendingChanges = true
d.propertiesToFetch = [\.name, \.startDate]
d.relationshipKeyPathsForPrefetching = [\.accommodation]
let trips = try modelContext.fetch(d)
let count = try modelContext.fetchCount(d)
let ids = try modelContext.fetchIdentifiers(d)
try modelContext.enumerate(d, batchSize: 1000) { trip in trip.isProcessed = true }swift
var d = FetchDescriptor<Trip>(predicate: ..., sortBy: [...])
d.fetchLimit = 20; d.fetchOffset = 0
d.includePendingChanges = true
d.propertiesToFetch = [\.name, \.startDate]
d.relationshipKeyPathsForPrefetching = [\.accommodation]
let trips = try modelContext.fetch(d)
let count = try modelContext.fetchCount(d)
let ids = try modelContext.fetchIdentifiers(d)
try modelContext.enumerate(d, batchSize: 1000) { trip in trip.isProcessed = true }Schema Versioning and Migration
模式版本控制与迁移
swift
enum SchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] { [Trip.self] }
@Model class Trip { var name: String; init(name: String) { self.name = name } }
}
enum SchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] { [Trip.self] }
@Model class Trip {
var name: String; var startDate: Date? // New property
init(name: String) { self.name = name }
}
}
enum TripMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] }
static var stages: [MigrationStage] { [migrateV1toV2] }
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: SchemaV1.self, toVersion: SchemaV2.self)
}
// Custom migration for data transformation
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: SchemaV2.self, toVersion: SchemaV3.self,
willMigrate: nil,
didMigrate: { context in
let trips = try context.fetch(FetchDescriptor<SchemaV3.Trip>())
for trip in trips { trip.displayName = trip.name.capitalized }
try context.save()
})Lightweight handles: adding optional/defaulted properties, renaming (), removing properties, adding model types.
originalNameswift
enum SchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] { [Trip.self] }
@Model class Trip { var name: String; init(name: String) { self.name = name } }
}
enum SchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] { [Trip.self] }
@Model class Trip {
var name: String; var startDate: Date? // 新增属性
init(name: String) { self.name = name }
}
}
enum TripMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] }
static var stages: [MigrationStage] { [migrateV1toV2] }
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: SchemaV1.self, toVersion: SchemaV2.self)
}
// 自定义迁移实现数据转换
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: SchemaV2.self, toVersion: SchemaV3.self,
willMigrate: nil,
didMigrate: { context in
let trips = try context.fetch(FetchDescriptor<SchemaV3.Trip>())
for trip in trips { trip.displayName = trip.name.capitalized }
try context.save()
})轻量迁移支持的场景:新增可选/带默认值的属性、属性重命名(通过)、删除属性、新增模型类型。
originalNameConcurrency (@ModelActor)
并发处理(@ModelActor)
swift
@ModelActor
actor DataHandler {
func importTrips(_ records: [TripRecord]) throws {
for r in records {
modelContext.insert(Trip(name: r.name, destination: r.dest,
startDate: r.start, endDate: r.end))
}
try modelContext.save() // Always save explicitly in @ModelActor
}
func process(tripID: PersistentIdentifier) throws {
guard let trip = self[tripID, as: Trip.self] else { return }
trip.isProcessed = true; try modelContext.save()
}
}
let handler = DataHandler(modelContainer: container)
try await handler.importTrips(records)Rules: is . is NOT -- use on its creating actor. Pass (Sendable) across boundaries. Never pass objects across actors.
ModelContainerSendableModelContextPersistentIdentifier@Modelswift
@ModelActor
actor DataHandler {
func importTrips(_ records: [TripRecord]) throws {
for r in records {
modelContext.insert(Trip(name: r.name, destination: r.dest,
startDate: r.start, endDate: r.end))
}
try modelContext.save() // 在@ModelActor中必须显式调用保存
}
func process(tripID: PersistentIdentifier) throws {
guard let trip = self[tripID, as: Trip.self] else { return }
trip.isProcessed = true; try modelContext.save()
}
}
let handler = DataHandler(modelContainer: container)
try await handler.importTrips(records)规则:遵循协议可跨actor传递。不遵循该协议,仅能在创建它的actor中使用。跨边界传递时使用(遵循Sendable),禁止跨actor传递对象。
ModelContainerSendableModelContextPersistentIdentifier@ModelSwiftUI Integration
SwiftUI集成
swift
@main
struct MyApp: App {
var body: some Scene {
WindowGroup { ContentView() }
.modelContainer(for: [Trip.self, LivingAccommodation.self])
}
}
struct DetailView: View {
@Environment(\.modelContext) private var modelContext
let trip: Trip
var body: some View {
Text(trip.name)
Button("Delete") { modelContext.delete(trip) }
}
}
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Trip.self, configurations: config)
container.mainContext.insert(Trip(name: "Preview", destination: "London",
startDate: .now, endDate: .now + 86400))
return TripListView().modelContainer(container)
}swift
@main
struct MyApp: App {
var body: some Scene {
WindowGroup { ContentView() }
.modelContainer(for: [Trip.self, LivingAccommodation.self])
}
}
struct DetailView: View {
@Environment(\.modelContext) private var modelContext
let trip: Trip
var body: some View {
Text(trip.name)
Button("Delete") { modelContext.delete(trip) }
}
}
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Trip.self, configurations: config)
container.mainContext.insert(Trip(name: "Preview", destination: "London",
startDate: .now, endDate: .now + 86400))
return TripListView().modelContainer(container)
}Common Mistakes
常见错误
1. @Model on struct -- Use class. requires reference semantics.
@Model2. @Transient without default -- Always provide default: .
@Transient var x: Bool = false3. Missing .modelContainer -- @Query returns empty without a container on the view hierarchy.
4. Passing model objects across actors:
swift
// WRONG: await handler.process(trip: trip)
// CORRECT: await handler.process(tripID: trip.persistentModelID)5. ModelContext on wrong actor:
swift
// WRONG: Task.detached { context.fetch(...) }
// CORRECT: Use @ModelActor for background work6. Unsupported #Predicate expressions:
swift
// WRONG: #Predicate<Trip> { $0.name.uppercased() == "PARIS" }
// CORRECT: #Predicate<Trip> { $0.name.localizedStandardContains("paris") }7. Flow control in #Predicate:
swift
// WRONG: #Predicate<Trip> { for tag in $0.tags { ... } }
// CORRECT: #Predicate<Trip> { $0.tags.contains { $0.name == "x" } }8. No save in @ModelActor -- Always call explicitly.
try modelContext.save()9. ObservableObject with @Model -- Never use /. generates . Use in views.
ObservableObject@Published@ModelObservable@Query10. Non-optional relationship without default:
swift
// WRONG: var accommodation: LivingAccommodation // crashes on reconstitution
// CORRECT: var accommodation: LivingAccommodation?11. Cascade without inverse -- Specify for reliable cascade delete behavior.
inverse:12. DispatchQueue for background data work:
swift
// WRONG: DispatchQueue.global().async { ModelContext(container).fetch(...) }
// CORRECT: @ModelActor actor Handler { func fetch() throws { ... } }1. 给结构体加@Model -- 必须用类,依赖引用语义。
@Model2. @Transient修饰的属性没有默认值 -- 必须提供默认值:。
@Transient var x: Bool = false3. 缺少.modelContainer配置 -- 视图层级上没有绑定容器时,@Query会返回空结果。
4. 跨actor传递模型对象:
swift
// 错误: await handler.process(trip: trip)
// 正确: await handler.process(tripID: trip.persistentModelID)5. 在错误的actor中使用ModelContext:
swift
// 错误: Task.detached { context.fetch(...) }
// 正确: 后台任务使用@ModelActor实现6. #Predicate使用不支持的表达式:
swift
// 错误: #Predicate<Trip> { $0.name.uppercased() == "PARIS" }
// 正确: #Predicate<Trip> { $0.name.localizedStandardContains("paris") }7. #Predicate中使用流程控制语句:
swift
// 错误: #Predicate<Trip> { for tag in $0.tags { ... } }
// 正确: #Predicate<Trip> { $0.tags.contains { $0.name == "x" } }8. @ModelActor中未调用保存方法 -- 必须显式调用。
try modelContext.save()9. @Model类搭配ObservableObject使用 -- 不要使用/,会自动生成实现,视图中直接使用即可。
ObservableObject@Published@ModelObservable@Query10. 非可选关联类型没有默认值:
swift
// 错误: var accommodation: LivingAccommodation // 数据重构时会崩溃
// 正确: var accommodation: LivingAccommodation?11. 级联删除没有配置反向关联 -- 指定保证级联删除行为稳定。
inverse:12. 使用DispatchQueue处理后台数据任务:
swift
// 错误: DispatchQueue.global().async { ModelContext(container).fetch(...) }
// 正确: @ModelActor actor Handler { func fetch() throws { ... } }Review Checklist
审查检查清单
- Every is a class with a designated initializer
@Model - All properties have default values
@Transient - Relationships specify and
deleteRuleinverse - attached at scene/root view level
.modelContainer - used for reactive data display in SwiftUI
@Query - uses only supported operators
#Predicate - Background work uses
@ModelActor - used across actor boundaries
PersistentIdentifier - Schema changes have +
VersionedSchemaSchemaMigrationPlan - Large data uses
@Attribute(.externalStorage) - CloudKit models use optionals and avoid unique constraints
- Explicit in
save()methods@ModelActor - on frequently queried properties (iOS 18+)
#Index - Previews use
ModelConfiguration(isStoredInMemoryOnly: true) - classes accessed from SwiftUI views are on
@Modelvia@MainActoror MainActor isolation@ModelActor
- 所有修饰的都是类,且包含指定构造器
@Model - 所有属性都有默认值
@Transient - 关联关系都指定了和
deleteRuleinverse - 绑定在场景/根视图层级
.modelContainer - SwiftUI中使用实现响应式数据展示
@Query - 仅使用支持的运算符
#Predicate - 后台任务使用实现
@ModelActor - 跨actor边界使用传递数据
PersistentIdentifier - 模式变更配套有+
VersionedSchemaSchemaMigrationPlan - 大数据使用存储
@Attribute(.externalStorage) - CloudKit关联的模型使用可选类型,避免唯一性约束
- 方法中显式调用
@ModelActorsave() - 高频查询的属性配置了(iOS 18+)
#Index - 预览使用
ModelConfiguration(isStoredInMemoryOnly: true) - SwiftUI视图中访问的类通过
@Model或MainActor隔离保证运行在@ModelActor上@MainActor
Reference Material
参考资料
- See for custom data stores, history tracking, CloudKit, Core Data coexistence, composite attributes, model inheritance, undo/redo, and performance patterns.
references/swiftdata-advanced.md - See for @Query variants, FetchDescriptor deep dive, sectioned queries, dynamic queries, and background fetch patterns.
references/swiftdata-queries.md
- 查看了解自定义数据存储、历史追踪、CloudKit、Core Data共存、复合属性、模型继承、撤销/重做以及性能优化模式。
references/swiftdata-advanced.md - 查看了解@Query变体、FetchDescriptor深度用法、分段查询、动态查询以及后台查询模式。
references/swiftdata-queries.md