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 App 中使用 WebKit for SwiftUI 嵌入與控制網頁內容。

Software Development1.1K2 個月前更新