swiftdata

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SwiftData

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
@Model
to a class (not struct). Generates
PersistentModel
,
Observable
,
Sendable
.
swift
@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:
.externalStorage
,
.unique
,
.spotlight
,
.allowsCloudEncryption
,
.preserveValueOnDeletion
(iOS 18+),
.ephemeral
,
.transformable(by:)
. Rename:
@Attribute(originalName: "old_name")
.
@Relationship:
deleteRule:
.cascade
/
.nullify
(default)/
.deny
/
.noAction
. Specify
inverse:
for reliable behavior. Unidirectional (iOS 18+):
inverse: nil
.
#Unique (iOS 18+):
#Unique<Person>([\.firstName, \.lastName])
-- compound uniqueness.
#Index (iOS 18+):
#Index<Trip>([\.name], [\.startDate, \.endDate])
-- query indexes.
Inheritance (iOS 26+):
@Model class BusinessTrip: Trip { var company: String }
.
Supported types:
Bool
,
Int
/
UInt
variants,
Float
,
Double
,
String
,
Date
,
Data
,
URL
,
UUID
,
Decimal
,
Array
,
Dictionary
,
Set
,
Codable
enums,
Codable
structs (composite, iOS 18+), relationships to
@Model
classes.
@Model
应用到(而非结构体)上,会自动生成
PersistentModel
Observable
Sendable
协议实现。
swift
@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可选配置
.externalStorage
.unique
.spotlight
.allowsCloudEncryption
.preserveValueOnDeletion
(iOS 18+)、
.ephemeral
.transformable(by:)
。重命名用法:
@Attribute(originalName: "old_name")
@Relationship配置
deleteRule:
可选
.cascade
/
.nullify
(默认)/
.deny
/
.noAction
。建议指定
inverse:
保证关联行为稳定。单向关联(iOS 18+):
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 }
支持的类型:
Bool
Int
/
UInt
各类变体、
Float
Double
String
Date
Data
URL
UUID
Decimal
Array
Dictionary
Set
、遵循
Codable
的枚举、遵循
Codable
的结构体(复合类型,iOS 18+)、指向其他
@Model
类的关联关系。

ModelContainer 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" } }        // Collection
Supported:
==
,
!=
,
<
,
<=
,
>
,
>=
,
&&
,
||
,
!
,
contains()
,
allSatisfy()
,
filter()
,
starts(with:)
,
localizedStandardContains()
,
caseInsensitiveCompare()
, arithmetic, ternary, optional chaining, nil coalescing, type casting. Not supported: flow control, nested declarations, arbitrary method calls.
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 (
originalName
), removing properties, adding model types.
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?  // 新增属性
        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()
    })
轻量迁移支持的场景:新增可选/带默认值的属性、属性重命名(通过
originalName
)、删除属性、新增模型类型。

Concurrency (@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:
ModelContainer
is
Sendable
.
ModelContext
is NOT -- use on its creating actor. Pass
PersistentIdentifier
(Sendable) across boundaries. Never pass
@Model
objects across actors.
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()  // 在@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)
规则
ModelContainer
遵循
Sendable
协议可跨actor传递。
ModelContext
不遵循该协议,仅能在创建它的actor中使用。跨边界传递时使用
PersistentIdentifier
(遵循Sendable),禁止跨actor传递
@Model
对象。

SwiftUI 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.
@Model
requires reference semantics.
2. @Transient without default -- Always provide default:
@Transient var x: Bool = false
.
3. 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 work
6. 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
try modelContext.save()
explicitly.
9. ObservableObject with @Model -- Never use
ObservableObject
/
@Published
.
@Model
generates
Observable
. Use
@Query
in views.
10. Non-optional relationship without default:
swift
// WRONG: var accommodation: LivingAccommodation  // crashes on reconstitution
// CORRECT: var accommodation: LivingAccommodation?
11. Cascade without inverse -- Specify
inverse:
for reliable cascade delete behavior.
12. DispatchQueue for background data work:
swift
// WRONG: DispatchQueue.global().async { ModelContext(container).fetch(...) }
// CORRECT: @ModelActor actor Handler { func fetch() throws { ... } }
1. 给结构体加@Model -- 必须用类,
@Model
依赖引用语义。
2. @Transient修饰的属性没有默认值 -- 必须提供默认值:
@Transient var x: Bool = false
3. 缺少.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
@Model
会自动生成
Observable
实现,视图中直接使用
@Query
即可。
10. 非可选关联类型没有默认值:
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
    @Model
    is a class with a designated initializer
  • All
    @Transient
    properties have default values
  • Relationships specify
    deleteRule
    and
    inverse
  • .modelContainer
    attached at scene/root view level
  • @Query
    used for reactive data display in SwiftUI
  • #Predicate
    uses only supported operators
  • Background work uses
    @ModelActor
  • PersistentIdentifier
    used across actor boundaries
  • Schema changes have
    VersionedSchema
    +
    SchemaMigrationPlan
  • Large data uses
    @Attribute(.externalStorage)
  • CloudKit models use optionals and avoid unique constraints
  • Explicit
    save()
    in
    @ModelActor
    methods
  • #Index
    on frequently queried properties (iOS 18+)
  • Previews use
    ModelConfiguration(isStoredInMemoryOnly: true)
  • @Model
    classes accessed from SwiftUI views are on
    @MainActor
    via
    @ModelActor
    or MainActor isolation
  • 所有
    @Model
    修饰的都是类,且包含指定构造器
  • 所有
    @Transient
    属性都有默认值
  • 关联关系都指定了
    deleteRule
    inverse
  • .modelContainer
    绑定在场景/根视图层级
  • SwiftUI中使用
    @Query
    实现响应式数据展示
  • #Predicate
    仅使用支持的运算符
  • 后台任务使用
    @ModelActor
    实现
  • 跨actor边界使用
    PersistentIdentifier
    传递数据
  • 模式变更配套有
    VersionedSchema
    +
    SchemaMigrationPlan
  • 大数据使用
    @Attribute(.externalStorage)
    存储
  • CloudKit关联的模型使用可选类型,避免唯一性约束
  • @ModelActor
    方法中显式调用
    save()
  • 高频查询的属性配置了
    #Index
    (iOS 18+)
  • 预览使用
    ModelConfiguration(isStoredInMemoryOnly: true)
  • SwiftUI视图中访问的
    @Model
    类通过
    @ModelActor
    或MainActor隔离保证运行在
    @MainActor

Reference Material

参考资料

  • See
    references/swiftdata-advanced.md
    for custom data stores, history tracking, CloudKit, Core Data coexistence, composite attributes, model inheritance, undo/redo, and performance patterns.
  • See
    references/swiftdata-queries.md
    for @Query variants, FetchDescriptor deep dive, sectioned queries, dynamic queries, and background fetch patterns.
  • 查看
    references/swiftdata-advanced.md
    了解自定义数据存储、历史追踪、CloudKit、Core Data共存、复合属性、模型继承、撤销/重做以及性能优化模式。
  • 查看
    references/swiftdata-queries.md
    了解@Query变体、FetchDescriptor深度用法、分段查询、动态查询以及后台查询模式。