axiom-sqlitedata-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Migrating from SwiftData to SQLiteData

从SwiftData迁移到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)   │
│                                                         │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ 我是否应该从SwiftData切换到SQLiteData?                 │
├─────────────────────────────────────────────────────────┤
│                                                         │
│ 处理1万条以上记录时存在性能问题?                        │
│    是 → 使用SQLiteData(处理大型数据集时速度快10-50倍)  │
│                                                         │
│ 需要CloudKit记录共享(而非仅同步)?                     │
│    是 → 使用SQLiteData(SwiftData不支持记录共享)        │
│                                                         │
│ 需要跨多张表执行复杂查询?                              │
│    是 → 使用SQLiteData + 必要时使用原生GRDB              │
│                                                         │
│ 需要为Swift 6并发模型提供Sendable类型的模型?            │
│    是 → 使用SQLiteData(值类型,而非类类型)            │
│                                                         │
│ 测试@Model类的过程很繁琐?                              │
│    是 → 使用SQLiteData(纯结构体,易于模拟)            │
│                                                         │
│ 对于简单CRUD操作,SwiftData使用体验良好?                │
│    是 → 继续使用SwiftData(基础应用场景下更简单)        │
│                                                         │
└─────────────────────────────────────────────────────────┘

Pattern Equivalents

模式对应关系

SwiftDataSQLiteData
@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 = }

SwiftDataSQLiteData
@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)
swift
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)
swift
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

SwiftData(迁移前)
swift
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(迁移后)
swift
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)
        }
    }
}
核心差异:
  • class
    → 带
    nonisolated
    struct
  • @Model
    @Table
  • @Query
    @FetchAll
  • @Environment(\.modelContext)
    @Dependency(\.defaultDatabase)
  • 隐式保存 → 显式的
    database.write { }
    代码块
  • 直接初始化 → 使用
    .Draft
    类型执行插入操作
  • @Relationship
    → 显式外键 + 关联查询

CloudKit Sharing (SwiftData Can't Do This)

CloudKit共享(SwiftData不支持此功能)

SwiftData supports CloudKit sync but NOT sharing. SQLiteData is the only Apple-native option for record sharing.
swift
// 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.

SwiftData支持CloudKit同步但不支持共享。SQLiteData是目前唯一支持记录共享的苹果原生方案。
swift
// 1. 配置带共享功能的SyncEngine
prepareDependencies {
    $0.defaultDatabase = try! appDatabase()
    $0.defaultSyncEngine = try SyncEngine(
        for: $0.defaultDatabase,
        tables: Task.self, Project.self
    )
}

// 2. 共享一条记录
@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. 展示原生共享界面
.sheet(item: $sharedRecord) { record in
    CloudSharingView(sharedRecord: record)
}
共享功能可实现: 协作列表、共享工作区、家庭共享、团队协作功能。

Performance Comparison

性能对比

OperationSwiftDataSQLiteDataImprovement
Insert 50k records~4 minutes~45 seconds5x
Query 10k with predicate~2 seconds~50ms40x
Memory (10k objects)~80MB~20MB4x smaller
Cold launch (large DB)~3 seconds~200ms15x
Benchmarks approximate, vary by device and data shape.

操作SwiftDataSQLiteData性能提升
插入5万条记录~4分钟~45秒5倍
带谓词查询1万条记录~2秒~50毫秒40倍
内存占用(1万个对象)~80MB~20MB缩小4倍
冷启动(大型数据库)~3秒~200毫秒15倍
基准测试结果为近似值,具体取决于设备和数据结构。

Gradual Migration Strategy

渐进式迁移策略

You don't have to migrate everything at once:
  1. Add SQLiteData for new features — Keep SwiftData for existing simple CRUD
  2. Migrate one model at a time — Start with the performance bottleneck
  3. Use separate databases initially — SQLiteData for heavy data/sharing, SwiftData for preferences
  4. Consolidate if needed — Or keep hybrid if it works

你无需一次性完成全部迁移:
  1. 为新功能引入SQLiteData — 现有简单CRUD操作继续使用SwiftData
  2. 逐个迁移模型 — 从性能瓶颈对应的模型开始迁移
  3. 初始阶段使用独立数据库 — SQLiteData处理大数据/共享场景,SwiftData处理偏好设置
  4. 按需合并 — 如果混合架构运行良好,也可保持现状

Common Gotchas

常见注意事项

Relationships → Foreign Keys

关联关系 → 外键

swift
// 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 }
swift
// SwiftData: 隐式关联
@Relationship var tasks: [Task]

// SQLiteData: 显式列 + 查询
// 子模型中:var projectID: Project.ID
// 查询方式:Task.where { $0.projectID == project.id }

Cascade Deletes

级联删除

swift
// SwiftData: @Relationship(deleteRule: .cascade)

// SQLiteData: Define in SQL schema
// "REFERENCES parent(id) ON DELETE CASCADE"
swift
// SwiftData: @Relationship(deleteRule: .cascade)

// SQLiteData: 在SQL schema中定义
// "REFERENCES parent(id) ON DELETE CASCADE"

No Automatic Inverse

无自动反向关联

swift
// 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
swift
// SwiftData: @Relationship(inverse: \Task.project)

// SQLiteData: 手动查询两个方向
let tasks = Task.where { $0.projectID == project.id }
let project = Project.find(task.projectID)

相关技能:
  • axiom-sqlitedata
    — 完整的SQLiteData API参考
  • axiom-swiftdata
    — 若继续使用苹果框架,可参考SwiftData相关模式
  • axiom-grdb
    — 复杂查询可使用原生GRDB

历史记录: 查看git日志了解变更