Migrating from SwiftData to SQLiteData When to Switch ┌─────────────────────────────────────────────────────────┐ │ Should I switch from SwiftData to SQLiteData? │ ├─────────────────────────────────────────────────────────┤ │ │ │ Performance problems with 10k+ records? │ │ YES → SQLiteData (10-50x faster for large datasets) │ │ │ │ Need CloudKit record SHARING (not just sync)? │ │ YES → SQLiteData (SwiftData cannot share records) │ │ │ │ Complex queries across multiple tables? │ │ YES → SQLiteData + raw GRDB when needed │ │ │ │ Need Sendable models for Swift 6 concurrency? │ │ YES → SQLiteData (value types, not classes) │ │ │ │ Testing @Model classes is painful? │ │ YES → SQLiteData (pure structs, easy to mock) │ │ │ │ Happy with SwiftData for simple CRUD? │ │ YES → Stay with SwiftData (simpler for basic apps) │ │ │ └─────────────────────────────────────────────────────────┘
Pattern Equivalents SwiftData SQLiteData @Model class Item @Table nonisolated struct Item @Attribute(.unique) @Column(primaryKey: true) or SQL UNIQUE @Relationship var tags: [Tag] var tagIDs: [Tag.ID] + join query @Query var items: [Item] @FetchAll var items: [Item] @Query(sort: .title) @FetchAll(Item.order(by: .title)) @Query(filter: #Predicate { $0.isActive }) @FetchAll(Item.where(.isActive)) @Environment(.modelContext) @Dependency(.defaultDatabase) context.insert(item) Item.insert { Item.Draft(...) }.execute(db) context.delete(item) Item.find(id).delete().execute(db) try context.save() Automatic in database.write { } block ModelContainer(for:) prepareDependencies { $0.defaultDatabase = } Code Example
SwiftData (Before)
import SwiftData
@Model class Task { var id: UUID var title: String var isCompleted: Bool var project: Project?
init(title: String) {
self.id = UUID()
self.title = title
self.isCompleted = false
}
}
struct TaskListView: View { @Environment(.modelContext) private var context @Query(sort: .title) private var tasks: [Task]
var body: some View {
List(tasks) { task in
Text(task.title)
}
}
func addTask(_ title: String) {
let task = Task(title: title)
context.insert(task)
}
func deleteTask(_ task: Task) {
context.delete(task)
}
}
SQLiteData (After)
import SQLiteData
@Table nonisolated struct Task: Identifiable { let id: UUID var title = "" var isCompleted = false var projectID: Project.ID? }
struct TaskListView: View { @Dependency(.defaultDatabase) var database @FetchAll(Task.order(by: .title)) var tasks
var body: some View {
List(tasks) { task in
Text(task.title)
}
}
func addTask(_ title: String) {
try database.write { db in
try Task.insert {
Task.Draft(title: title)
}
.execute(db)
}
}
func deleteTask(_ task: Task) {
try database.write { db in
try Task.find(task.id).delete().execute(db)
}
}
}
Key differences:
class → struct with nonisolated @Model → @Table @Query → @FetchAll @Environment(.modelContext) → @Dependency(.defaultDatabase) Implicit save → Explicit database.write { } block Direct init → .Draft type for inserts @Relationship → Explicit foreign key + join CloudKit Sharing (SwiftData Can't Do This)
SwiftData supports CloudKit sync but NOT sharing. SQLiteData is the only Apple-native option for record sharing.
// 1. Setup SyncEngine with sharing prepareDependencies { $0.defaultDatabase = try! appDatabase() $0.defaultSyncEngine = try SyncEngine( for: $0.defaultDatabase, tables: Task.self, Project.self ) }
// 2. Share a record @Dependency(.defaultSyncEngine) var syncEngine @State var sharedRecord: SharedRecord?
func shareProject(_ project: Project) async throws { sharedRecord = try await syncEngine.share(record: project) { share in share[CKShare.SystemFieldKey.title] = "Join my project!" } }
// 3. Present native sharing UI .sheet(item: $sharedRecord) { record in CloudSharingView(sharedRecord: record) }
Sharing enables: Collaborative lists, shared workspaces, family sharing, team features.
Performance Comparison Operation SwiftData SQLiteData Improvement Insert 50k records ~4 minutes ~45 seconds 5x Query 10k with predicate ~2 seconds ~50ms 40x Memory (10k objects) ~80MB ~20MB 4x smaller Cold launch (large DB) ~3 seconds ~200ms 15x
Benchmarks approximate, vary by device and data shape.
Gradual Migration Strategy
You don't have to migrate everything at once:
Add SQLiteData for new features — Keep SwiftData for existing simple CRUD Migrate one model at a time — Start with the performance bottleneck Use separate databases initially — SQLiteData for heavy data/sharing, SwiftData for preferences Consolidate if needed — Or keep hybrid if it works Common Gotchas Relationships → Foreign Keys // SwiftData: implicit relationship @Relationship var tasks: [Task]
// SQLiteData: explicit column + query // In child: var projectID: Project.ID // To fetch: Task.where { $0.projectID == project.id }
Cascade Deletes // SwiftData: @Relationship(deleteRule: .cascade)
// SQLiteData: Define in SQL schema // "REFERENCES parent(id) ON DELETE CASCADE"
No Automatic Inverse // SwiftData: @Relationship(inverse: \Task.project)
// SQLiteData: Query both directions manually let tasks = Task.where { $0.projectID == project.id } let project = Project.find(task.projectID)
Related Skills:
axiom-sqlitedata — Full SQLiteData API reference axiom-swiftdata — SwiftData patterns if staying with Apple's framework axiom-grdb — Raw GRDB for complex queries
History: See git log for changes