Skip to main content

swiftdata-persistence-expert

SwiftData and Core Data persistence architecture, schema design, model relationships, CloudKit sync, and safe multi-version database migrations (VersionedSchema & SchemaMigrationPlan). Use when designing data persistence, fixing store corruption, or upgrading app schema between releases.

Source facts

Repository
ankb90/ios-cli
Last source activity
November 9, 2026 at 05:03
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
swiftdata-persistence-expert
description
SwiftData and Core Data persistence architecture, schema design, model relationships, CloudKit sync, and safe multi-version database migrations (VersionedSchema & SchemaMigrationPlan). Use when designing data persistence, fixing store corruption, or upgrading app schema between releases.
# SwiftData & Core Data Persistence Specialist This skill provides authoritative guidance on implementing, optimizing, and migrating data persistence using **SwiftData** and **Core Data**, based on Apple's official documentation and WWDC architectural standards. --- ## 1. Schema Design Standards (`@Model`) ### A. Model Definition Rules 1. **Always provide default values:** If CloudKit sync is enabled, every property must either be optional or have an explicit default value. 2. **Define explicit delete rules:** Never leave `@Relationship` delete rules implicit. Always choose `.cascade`, `.nullify`, or `.deny`. 3. **Use `@Transient` for computed or runtime-only state:** Properties marked `@Transient` are never written to SQLite disk. ```swift import SwiftData import Foundation @Model public final class Project { public var id: UUID = UUID() public var title: String = "" public var createdAt: Date = Date.now public var isArchived: Bool = false // Explicit relationship with delete rule @Relationship(deleteRule: .cascade, inverse: \TaskItem.project) public var tasks: [TaskItem] = [] // Transient property: Not saved to SQLite @Transient public var isSelectedForBatchAction: Bool = false public init(title: String) { self.title = title } } ``` --- ## 2. Safe Schema Migrations (`VersionedSchema` & `SchemaMigrationPlan`) When updating an application from Version 1 to Version 2, unversioned schema modifications can cause **fatal startup crashes** (`Store could not be loaded`). Always define explicit `VersionedSchema` namespaces: ### Step 1: Define Schemas per Version ```swift // Version 1 Schema public enum AppSchemaV1: VersionedSchema { public static var versionIdentifier: Schema.Version = Schema.Version(1, 0, 0) public static var models: [any PersistentModel.Type] { [Project.self, TaskItem.self] } @Model public final class Project { public var title: String = "" public init(title: String) { self.title = title } } } // Version 2 Schema (Added 'priority' field, renamed 'title' to 'name') public enum AppSchemaV2: VersionedSchema { public static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0) public static var models: [any PersistentModel.Type] { [Project.self, TaskItem.self] } @Model public final class Project { public var name: String = "" public var priority: Int = 1 public init(name: String, priority: Int = 1) { self.name = name self.priority = priority } } } ``` ### Step 2: Define Migration Plan ```swift public enum AppMigrationPlan: SchemaMigrationPlan { public static var schemas: [any VersionedSchema.Type] { [AppSchemaV1.self, AppSchemaV2.self] } public static var stages: [MigrationStage] { [migrateV1toV2] } public static let migrateV1toV2 = MigrationStage.custom( fromVersion: AppSchemaV1.self, toVersion: AppSchemaV2.self, willMigrate: { context in // Optional pre-migration validation }, didMigrate: { context in // Data transformation: populate new 'name' from old 'title' let projects = try context.fetch(FetchDescriptor<AppSchemaV2.Project>()) for project in projects { if project.priority == 0 { project.priority = 1 } } try context.save() } ) } ``` ### Step 3: Initialize ModelContainer with Migration Plan ```swift let container = try ModelContainer( for: AppSchemaV2.Project.self, migrationPlan: AppMigrationPlan.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false) ) ``` --- ## 3. Concurrency with SwiftData (`@ModelActor`) **CRITICAL RULE:** `ModelContext` is **NOT thread-safe**. Never access or pass a `ModelContext` across different threads or actors. To perform background imports, bulk syncs, or intensive calculations without freezing the UI, use the **`@ModelActor`** macro: ```swift import SwiftData @ModelActor public actor BackgroundDataImporter { public func importRemoteRecords(_ records: [RemoteRecordDTO]) throws { // modelContext and modelExecutor are synthesized by @ModelActor for record in records { let entity = Project(title: record.name) modelContext.insert(entity) } try modelContext.save() } } ``` --- ## 4. SwiftData Verification Checklist - [ ] **CloudKit Ready:** If using iCloud sync, verify no model uses `@Attribute(.unique)` and all fields have default values or are optional. - [ ] **No Cross-Thread Context Access:** Ensure `@Query` and `modelContext` in views stay on the `@MainActor`. All background mutations must use `@ModelActor`. - [ ] **Delete Rules Tested:** Verify what happens when a parent entity is deleted (do children cascade delete or nullify?). - [ ] **Migration Plan Active:** Any change to model fields in an existing app must increment `VersionedSchema` and be tested with an old store database file.
View on GitHub