SwiftData
Overview
Apple's native persistence framework using @Model classes and declarative queries. Built on Core Data, designed for SwiftUI.
Core principle Reference types (class) + @Model macro + declarative @Query for reactive SwiftUI integration.
Requires iOS 17+, Swift 5.9+
Target iOS 26+ (this skill focuses on latest features)
License Proprietary (Apple)
When to Use SwiftData
Choose SwiftData when you need
- β
Native Apple integration with SwiftUI
- β
Simple CRUD operations
- β
Automatic UI updates with
@Query
- β
CloudKit sync (iOS 17+)
- β
Reference types (classes) with relationships
Use SQLiteData instead when
- Need value types (structs)
- CloudKit record sharing (not just sync)
- Large datasets (50k+ records) with specific performance needs
Use GRDB when
- Complex raw SQL required
- Fine-grained migration control needed
For migrations See the axiom-swiftdata-migration skill for custom schema migrations with VersionedSchema and SchemaMigrationPlan. For migration debugging, see axiom-swiftdata-migration-diag.
Example Prompts
These are real questions developers ask that this skill is designed to answer:
Basic Operations
1. "I have a notes app with folders. I need to filter notes by folder and sort by last modified. How do I set up the @Query?"
β The skill shows how to use @Query with predicates, sorting, and automatic view updates
2. "When a user deletes a task list, all tasks should auto-delete too. How do I set up the relationship?"
β The skill explains @Relationship with deleteRule: .cascade and inverse relationships
3. "I have a relationship between User β Messages β Attachments. How do I prevent orphaned data when deleting?"
β The skill shows cascading deletes, inverse relationships, and safe deletion patterns
CloudKit & Sync
4. "My chat app syncs messages to other devices via CloudKit. Sometimes messages conflict. How do I handle sync conflicts?"
β The skill covers CloudKit integration, conflict resolution strategies (last-write-wins, custom resolution), and sync patterns
5. "I'm adding CloudKit sync to my app, but I get 'Property must have a default value' error. What's wrong?"
β The skill explains CloudKit constraints: all properties must be optional or have defaults, explains why (network timing), and shows fixes
6. "I want to show users when their data is syncing to iCloud and what happens when they're offline."
β The skill shows monitoring sync status with notifications, detecting network connectivity, and offline-aware UI patterns
7. "I need to share a playlist with other users. How do I implement CloudKit record sharing?"
β The skill covers CloudKit record sharing patterns (iOS 26+) with owner/permission tracking and sharing metadata
Performance & Optimization
8. "I need to query 50,000 messages but only display 20 at a time. How do I paginate efficiently?"
β The skill covers performance patterns, batch fetching, limiting queries, and preventing memory bloat with chunked imports
9. "My app loads 100 tasks with relationships, and displaying them is slow. I think it's N+1 queries."
β The skill shows how to identify N+1 problems without prefetching, provides prefetching pattern, and shows 100x performance improvement
10. "I'm importing 1 million records from an API. What's the best way to batch them without running out of memory?"
β The skill shows chunk-based importing with periodic saves, memory cleanup patterns, and batch operation optimization
11. "Which properties should I add indexes to? I'm worried about over-indexing slowing down writes."
β The skill explains index optimization patterns: when to index (frequently filtered/sorted properties), when to avoid (rarely used, frequently changing), maintenance costs
Migration from Legacy Frameworks
12. "We're migrating from Realm/Core Data to SwiftData"
β See the comparison table in Migration section below, then follow realm-to-swiftdata-migration or axiom-swiftdata-migration for detailed guides
@Model Definitions
Basic Model
import SwiftData
@Model
final class Track {
@Attribute(.unique) var id: String
var title: String
var artist: String
var duration: TimeInterval
var genre: String?
init(id: String, title: String, artist: String, duration: TimeInterval, genre: String? = nil) {
self.id = id
self.title = title
self.artist = artist
self.duration = duration
self.genre = genre
}
}
Key patterns
- Use
final class, not struct (omit final if you need subclasses β see Class Inheritance below)
- Use
@Attribute(.unique) for primary key-like behavior
- Provide explicit
init (SwiftData doesn't synthesize)
- Optional properties (
String?) are nullable
- Use
@Attribute(.preserveValueOnDeletion) on properties whose values should survive even after the object is deleted (useful for analytics, audit trails)
Relationships
@Model
final class Track {
@Attribute(.unique) var id: String
var title: String
@Relationship(deleteRule: .cascade, inverse: \Album.tracks)
var album: Album?
init(id: String, title: String, album: Album? = nil) {
self.id = id
self.title = title
self.album = album
}
}
@Model
final class Album {
@Attribute(.unique) var id: String
var title: String
@Relationship(deleteRule: .cascade)
var tracks: [Track] = []
init(id: String, title: String) {
self.id = id
self.title = title
}
}
Many-to-Many Self-Referential Relationships
@MainActor
@Model
final class User {
@Attribute(.unique) var id: String
var name: String
@Relationship(deleteRule: .nullify, inverse: \User.following)
var followers: [User] = []
@Relationship(deleteRule: .nullify)
var following: [User] = []
init(id: String, name: String) {
self.id = id
self.name = name
}
}
CRITICAL: SwiftData automatically manages BOTH sides when you modify ONE side.
β
Correct β Only modify ONE side
user1.following.append(user2)
try modelContext.save()
β Wrong β Don't manually update both sides
user1.following.append(user2)
user2.followers.append(user1)
Unfollowing (remove from ONE side only)
user1.following.removeAll { $0.id == user2.id }
try modelContext.save()
Verifying relationship integrity (for debugging)
let user1FollowsUser2 = user1.following.contains { $0.id == user2.id }
let user2FollowedByUser1 = user2.followers.contains { $0.id == user1.id }
assert(user1FollowsUser2 == user2FollowedByUser1, "Relationship corrupted!")
CloudKit Sync Recovery (if relationships become corrupted)
let backup = user.following.map { $0.id }
user.following.removeAll()
user.followers.removeAll()
try modelContext.save(