mcpField
A
axiom-sqlitedata-migration

axiom-sqlitedata-migration

github.com/charleswiltgen数据与数据库1.2k184 次安装今天更新
代码仓库

关于 axiom-sqlitedata-migration

这是一份面向应用开发者的迁移指南,介绍如何将数据从 SwiftData 迁移到 SQLiteData。内容包括常见 API 对照、CloudKit 记录共享与性能考量,以及保留数据和分阶段迁移的方法。

主要功能

  1. 01对照 SwiftData 与 SQLiteData 模式
  2. 02配置 CloudKit 记录共享
  3. 03比较性能特征
  4. 04导出并导入现有用户数据
  5. 05逐步迁移数据模型
  6. 06处理关系和级联删除

适用场景

  1. 01将性能瓶颈模型迁移到 SQLiteData
  2. 02为应用添加记录共享
  3. 03迁移时保留用户数据
  4. 04与 SwiftData 并行逐步采用 SQLiteData

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-sqlitedata-migration

安装后在 Claude Code 中输入/axiom-sqlitedata-migration确认已加载。

详细介绍

从 SwiftData 迁移到 SQLiteData

何时切换

┌─────────────────────────────────────────────────────────┐
│ 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)   │
│                                                         │
└─────────────────────────────────────────────────────────┘

模式对应关系

SwiftDataSQLiteData
@Model class Item@Table nonisolated struct Item
@Attribute(.unique)@Column(primaryKey: true) 或 SQL UNIQUE
@Relationship var tags: [Tag]var tagIDs: [Tag.ID] + 联接查询
@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()在 database.write { } 块中自动保存
ModelContainer(for:)prepareDependencies { $0.defaultDatabase = }

代码示例

SwiftData(之前)

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(之后)

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 共享(SwiftData 无法实现)

SwiftData 支持 CloudKit 同步,但不支持共享。SQLiteData 是唯一支持记录共享的 Apple 原生选项。

// 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)
}

共享功能可用于: 协作列表、共享工作区、家庭共享和团队功能。


性能比较

操作SwiftDataSQLiteData提升
插入 50k 条记录~4 分钟~45 秒5x
使用谓词查询 10k 条记录~2 秒~50ms40x
内存占用(10k 个对象)~80MB~20MB缩小 4 倍
冷启动(大型数据库)~3 秒~200ms15x

基准数据为近似值,因设备和数据形态而异。


迁移现有用户数据

重要: 仅进行架构迁移会丢失所有用户数据。你必须从 SwiftData 导出数据,再导入 SQLiteData。

// 1. Read all records from SwiftData's backing store
func migrateExistingData(from modelContext: ModelContext, to database: any DatabaseWriter) throws {
    // Fetch all SwiftData records
    let descriptor = FetchDescriptor<SwiftDataTask>()
    let existingTasks = try modelContext.fetch(descriptor)

    // 2. Bulk insert into SQLiteData
    try database.write { db in
        for task in existingTasks {
            try SQLiteTask.insert {
                SQLiteTask.Draft(
                    id: task.id,
                    title: task.title,
                    isCompleted: task.isCompleted,
                    projectID: task.project?.id
                )
            }
            .execute(db)
        }
    }

    // 3. Verify migration
    let count = try database.read { db in
        try SQLiteTask.fetchCount(db)
    }
    assert(count == existingTasks.count, "Migration count mismatch!")
}

迁移检查清单:

  • 删除 SwiftData 容器前导出所有模型
  • 迁移关系(获取父级 ID 作为外键)
  • 确认迁移前后的记录数一致
  • 在确认正常运行前保留 SwiftData 容器作为备份
  • 首次启动时使用 UserDefaults 中的版本标记运行迁移

渐进式迁移策略

不必一次性迁移所有内容:

  1. 为新功能添加 SQLiteData — 现有的简单 CRUD 功能继续使用 SwiftData
  2. 一次迁移一个模型 — 从性能瓶颈开始
  3. 初期使用独立数据库 — SQLiteData 用于大型数据和共享,SwiftData 用于偏好设置
  4. 按需整合 — 或者只要混合使用效果良好,就继续保持混合架构

常见注意事项

关系 → 外键
// SwiftData: implicit relationship
@Relationship var tasks: [Task]

// SQLiteData: explicit column + query
// In child: var projectID: Project.ID
// To fetch: Task.where { $0.projectID.eq(#bind(project.id)) }
级联删除
// SwiftData: @Relationship(deleteRule: .cascade)

// SQLiteData: Define in SQL schema
// "REFERENCES parent(id) ON DELETE CASCADE"
不会自动创建反向关系
// SwiftData: @Relationship(inverse: \Task.project)

// SQLiteData: Query both directions manually
let tasks = Task.where { $0.projectID.eq(#bind(project.id)) }
let project = Project.find(task.projectID)

相关技能:

  • axiom-sqlitedata — 完整的 SQLiteData API 参考
  • axiom-swiftdata — 如果继续使用 Apple 框架,可参考 SwiftData 模式
  • axiom-grdb — 用于复杂查询的原始 GRDB

历史记录: 更改记录请参阅 git log

评论

支持 Markdown