Swiftdata

作者 dpearson26998d90fd121a26无许可证1.1K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库2个月前更新

Implement, review, or improve data persistence using SwiftData. Use when defining @Model classes with @Attribute, @Relationship, @Transient, #Unique, or #Index; when querying with @Query, #Predicate, FetchDescriptor, or SortDescriptor; when configuring ModelContainer and ModelContext for SwiftUI or background work with @ModelActor; when planning schema migrations with VersionedSchema and SchemaMigrationPlan; when setting up CloudKit sync with ModelConfiguration; or when coexisting with or migrating from Core Data.

AI 生成的概览

指导在 iOS 应用中实现、审查和迁移 SwiftData 持久化,涵盖模型、查询、容器与 CloudKit。

功能
为使用 Swift 6.3 的 iOS 26+ 应用提供 SwiftData 持久化的参考指导与代码模式,涵盖 @Model 定义、@Attribute 与 @Relationship 选项、ModelContainer 与 ModelContext 配置、CRUD 操作、SwiftUI 中的 @Query、#Predicate、FetchDescriptor、模式版本与迁移、CloudKit 同步、@ModelActor 并发以及 Core Data 共存。它还列出常见错误和审查清单,并指向随附的参考文件,涉及高级主题、查询、谓词陷阱、索引和 Core Data 共存。其产出是指导与审查结论,而非可执行产物。
适用场景
适用于在 iOS 应用中定义或审查 SwiftData 模型、查询、容器、迁移或 CloudKit 同步时。也适用于让 SwiftData 与现有 Core Data 存储共存,或将界面从 Core Data 迁移到 SwiftData 的工作。
运行要求
无脚本,仅包含说明与参考文档。所述工作面向 iOS 26+ 与 Swift 6.3,并假定存在 Xcode 项目;CloudKit 同步指导假定已具备 iCloud 与 CloudKit 权限以及远程通知。

SwiftData

Persist, query, and manage structured data in iOS 26+ apps using SwiftData with Swift 6.3.

Contents

Model Definition

Apply @Model to a class (not struct). It synthesizes PersistentModel conformance. Model instances remain context/actor-bound; pass their PersistentIdentifier, not the instance, across actors.

swift
@Modelclass 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, .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.

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 and other compatible Codable value types, and relationships to @Model classes.

ModelContainer Setup

swift
// Basiclet container = try ModelContainer(for: Trip.self, LivingAccommodation.self)
// Configuredlet 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 planlet 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))

CloudKit Sync

ModelConfiguration(..., cloudKitDatabase:) opts a SwiftData store into automatic CloudKit sync, but app entitlements still gate sync.

For any SwiftData CloudKit setup or schema-review task, include a separate Capabilities verdict before schema findings:

  • Capabilities: Xcode target has the iCloud capability with CloudKit enabled and the intended container selected, plus Background Modes > Remote notifications. Without these entitlements, automatic sync is not fully configured even if cloudKitDatabase is set.
  • Schema compatibility: no @Attribute(.unique) or #Unique; relationships are optional, have explicit inverses where needed, and avoid .deny; large Data uses @Attribute(.externalStorage).
  • Scalar attributes: do not make every scalar optional just for CloudKit. Keep required scalars nonoptional when initializers, defaults, or migrations provide valid values.
  • Schema rollout: initialize the development schema only in nonproduction builds, verify it in CloudKit Dashboard, promote before release, and treat production changes as additive only.

CRUD Operations

For destructive batches and migrations, first run the exact predicate or version hop against a disposable copy and record affected identifiers/counts. Execute with explicit transaction/save semantics, refetch, and verify values, relationships, counts, and invariants. On failure, fix the predicate/schema and restore a pristine fixture before retrying; never blindly replay a destructive operation.

swift
// CREATElet trip = Trip(name: "Summer", destination: "Paris", startDate: .now, endDate: .now + 86400*7)modelContext.insert(trip)try modelContext.save()  // or rely on autosave
// READlet trips = try modelContext.fetch(FetchDescriptor<Trip>(    predicate: #Predicate { $0.destination == "Paris" },    sortBy: [SortDescriptor(\.startDate)]))
// UPDATE -- modify properties directly; autosave handles persistencetrip.destination = "Rome"
// DELETEmodelContext.delete(trip)try modelContext.delete(model: Trip.self, where: #Predicate { $0.isFavorite == false })
// TRANSACTION (atomic)try modelContext.transaction {    modelContext.insert(trip); trip.isFavorite = true}

@Query in SwiftUI

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 initstruct 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 querystruct 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

swift
#Predicate<Trip> { $0.destination.localizedStandardContains("paris") }  // Stringlet now = Date()#Predicate<Trip> { $0.startDate > 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, conditional expressions, optional chaining and binding, nil coalescing, type casting. Avoid: loops, nested declarations, mutations, and arbitrary unsupported method calls.

FetchDescriptor

swift
var d = FetchDescriptor<Trip>(predicate: ..., sortBy: [...])d.fetchLimit = 20; d.fetchOffset = 0d.includePendingChanges = trued.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 transformationstatic 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. Verify the stage list covers every supported version hop, then migrate a fresh copy of each old store and assert post-migration data before release.

Core Data Coexistence Boundary

Use this skill when the work is to run SwiftData alongside an existing Core Data store or migrate screens from Core Data to SwiftData over time. Keep pure Core Data stack setup, NSManagedObjectContext, NSFetchRequest, and batch Core Data operations in the sibling core-data skill.

For coexistence, give boundary guidance before detailed migration advice:

  • Point SwiftData and Core Data at the same SQLite store URL.
  • Match Core Data entity names, property names, types, and relationship shapes in the SwiftData @Model definitions.
  • Use @Attribute(originalName:) for SwiftData properties whose persisted Core Data names differ from the Swift names.
  • Do not write the same entity from both stacks at the same time; assign one stack as the writer for each entity during migration.

Concurrency (@ModelActor)

swift
@ModelActoractor 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.

SwiftUI Integration

swift
@mainstruct 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 { ... } }

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 avoid uniqueness, use optional relationships, avoid .deny, and do not blanket-optionalize scalars
  • CloudKit sync has iCloud + CloudKit, Remote notifications, and production schema rollout checked
  • Explicit save() in @ModelActor methods
  • Previews use ModelConfiguration(isStoredInMemoryOnly: true)
  • @Model classes accessed from SwiftUI views are on @MainActor via @ModelActor or MainActor isolation

References

  • references/swiftdata-advanced.md [blocked] — custom data stores, history tracking, CloudKit, composite attributes, model inheritance, undo/redo, performance
  • references/swiftdata-queries.md [blocked] — @Query variants, FetchDescriptor deep dive, sectioned queries, dynamic queries, background fetch
  • references/core-data-coexistence.md [blocked] — Core Data + SwiftData coexistence and migration boundaries
  • references/predicate-pitfalls.md [blocked] — #Predicate runtime crashes, unsupported expressions, safe patterns
  • references/indexing.md [blocked] — #Index macro, compound indexes, when to index, migration

来源与署名

来源:dpearson2699/swift-ios-skills位于skills/swiftdata提交8d90fd1

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架

更多来自 dpearson2699/swift-ios-skills 的技能

Widgetkit

dpearson2699

指导实现、审查和改进 iOS、iPadOS、watchOS 与 CarPlay 上的 WidgetKit 小组件和控制项。

Software Development1.1K2个月前更新

Weatherkit

dpearson2699

指导 iOS 开发者使用 WeatherService 获取 WeatherKit 预报、警报与署名信息。

Software Development1.1K2个月前更新

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

待分类1.1K2个月前更新

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

待分类1.1K2个月前更新

Tabletopkit

dpearson2699

指导使用 TabletopKit 在 visionOS 上构建多人空间桌游,涵盖棋具、座位、动作与 RealityKit 渲染。

Software Development1.1K2个月前更新

Swiftui Webkit

dpearson2699

指导在 iOS 26 及更高版本的 SwiftUI 应用中使用 WebKit for SwiftUI 嵌入和控制网页内容。

Software Development1.1K2个月前更新