S

SQLiteData 类型安全持久化指南

作者:鹿Sir开发工具v1

指导在 Swift 项目中使用 Point-Free 的 SQLiteData 做类型安全 SQLite 持久化:@Table 宏建模、@FetchAll/@FetchOne 响应式查询、CRUD 与 Upsert、#sql 原生 SQL、CloudKit 同步,以及在复杂场景下降级到 GRDB。当用户在 iOS/macOS 项目中询问 SQLiteData、SwiftData 替代、@Table 建模、CloudKit 数据同步、GRDB 查询写法时触发。触发词:SQLiteData、SwiftData、@Table、GRDB、CloudKit 同步、Swift 数据库、类型安全持久化。

下载量
395
点赞
95
价格
免费

技能文档

---
name: megastep-axiom-sqlitedata
title: SQLiteData 类型安全持久化指南
description: 指导在 Swift 项目中使用 Point-Free 的 SQLiteData 做类型安全 SQLite 持久化:@Table 宏建模、@FetchAll/@FetchOne 响应式查询、CRUD 与 Upsert、#sql 原生 SQL、CloudKit 同步,以及在复杂场景下降级到 GRDB。当用户在 iOS/macOS 项目中询问 SQLiteData、SwiftData 替代、@Table 建模、CloudKit 数据同步、GRDB 查询写法时触发。触发词:SQLiteData、SwiftData、@Table、GRDB、CloudKit 同步、Swift 数据库、类型安全持久化。
category: 开发工具
---

# SQLiteData

## 概述

基于 Point-Free 的 [SQLiteData](https://github.com/pointfreeco/sqlite-data) 做类型安全的 SQLite 持久化。它是 SwiftData 的快速轻量替代品,支持 CloudKit 同步,构建在 [GRDB](https://github.com/groue/GRDB.swift) 与 [StructuredQueries](https://github.com/pointfreeco/swift-structured-queries) 之上。

**核心原则**:值类型(`struct`)+ `@Table` 宏 + 所有写操作放在 `database.write { }` 闭包中。

**进阶模式**(CTE、视图、自定义聚合、schema 组合、Upsert、CloudKit 同步、GRDB 降级)见 [references/advanced-patterns.md](references/advanced-patterns.md)。

**要求**:iOS 17+,Swift 6 严格并发。

## 何时选择 SQLiteData

**需要以下能力时选 SQLiteData:**
- 编译器可检查查询的类型安全 SQLite
- 支持记录共享的 CloudKit 同步
- 大数据量(5 万条以上)下接近原生 SQLite 的性能
- 值类型(struct)而非 class
- Swift 6 严格并发支持

**以下情况改用 SwiftData:**
- 只需简单 CRUD 与 Apple 原生集成
- 偏好 `@Model` class 而非 struct
- 不需要 CloudKit 记录共享

**以下情况直接用 GRDB:**
- 跨 4 张以上表的复杂 SQL join
- 超出 schema 变更范围的自定义迁移逻辑
- 需要手写 SQL 的性能关键路径

## 快速参考

```swift
// 建模
@Table nonisolated struct Item: Identifiable {
    let id: UUID                    // 第一个 let = 自动主键
    var title = ""                  // 有默认值 = 非空列
    var notes: String?              // 可选 = 可空列
    @Column(as: Color.Hex.self)
    var color: Color = .blue        // 自定义类型表示
    @Ephemeral var isSelected = false  // 不持久化
}

// 依赖配置
prepareDependencies { $0.defaultDatabase = try! appDatabase() }
@Dependency(\.defaultDatabase) var database

// 查询
@FetchAll var items: [Item]
@FetchAll(Item.order(by: \.title).where(\.isInStock)) var items
@FetchOne(Item.count()) var count = 0

// 静态查询辅助(v1.4.0+)
try Item.fetchAll(db)              // 对比 Item.all.fetchAll(db)
try Item.find(db, key: id)         // 返回非可选 Item

// 插入
try database.write { db in
    try Item.insert { Item.Draft(title: "New") }.execute(db)
}

// 更新(单条)
try database.write { db in
    try Item.find(id).update { $0.title = "Updated" }.execute(db)
}

// 更新(批量)
try database.write { db in
    try Item.where(\.isInStock).update { $0.notes = "" }.execute(db)
}

// 删除
try database.write { db in
    try Item.find(id).delete().execute(db)
    try Item.where { $0.id.in(ids) }.delete().execute(db)  // 批量
}

// 条件与排序
Item.where(\.isActive)                     // keypath(简单)
Item.where { $0.title.contains("phone") }  // 闭包(复杂)
Item.where { $0.status.eq(#bind(.done)) }  // 枚举比较
Item.order(by: \.title)                    // 排序
Item.order { $0.createdAt.desc() }         // 降序
Item.limit(10).offset(20)                  // 分页

// 原生 SQL(#sql 宏)
#sql("SELECT * FROM items WHERE price > 100")  // 类型安全的原生 SQL
#sql("coalesce(date(\(dueDate)) = date(\(now)), 0)")  // 自定义表达式

// CloudKit 同步(v1.2-1.4+)
prepareDependencies {
    $0.defaultSyncEngine = try SyncEngine(
        for: $0.defaultDatabase,
        tables: Item.self
    )
}
@Dependency(\.defaultSyncEngine) var syncEngine

// 手动同步控制(v1.3.0+)
try await syncEngine.fetchChanges()  // 从 CloudKit 拉取
try await syncEngine.sendChanges()   // 推送到 CloudKit
try await syncEngine.syncChanges()   // 双向

// 同步状态观察(v1.2.0+)
syncEngine.isSendingChanges    // 上传中
syncEngine.isFetchingChanges   // 下载中
syncEngine.isSynchronizing     // 上传或下载中
```

## 常见反模式

### ❌ 在谓词中用 `==`
```swift
// 错误 —— 并非在所有上下文都可用
.where { $0.status == .completed }

// 正确 —— 使用比较方法
.where { $0.status.eq(#bind(.completed)) }
```

### ❌ 更新顺序错误
```swift
// 错误 —— .update 放在 .where 之前
Item.update { $0.title = "X" }.where { $0.id == id }

// 正确 —— 单条用 .find(),批量先 .where() 再 .update()
Item.find(id).update { $0.title = "X" }.execute(db)
Item.where(\.isOld).update { $0.archived = true }.execute(db)
```

### ❌ 用实例方法插入
```swift
// 错误 —— 没有实例 insert 方法
let item = Item(id: UUID(), title: "Test")
try item.insert(db)

// 正确 —— 用 .Draft 的静态 insert
try Item.insert { Item.Draft(title: "Test") }.execute(db)
```

### ❌ 漏写 `nonisolated`
```swift
// 错误 —— Swift 6 并发警告
@Table struct Item { ... }

// 正确
@Table nonisolated struct Item { ... }
```

### ❌ 在 write 闭包内 await
```swift
// 错误 —— write 闭包是同步的
try await database.write { db in ... }

// 正确 —— 闭包内不得出现 await
try database.write { db in
    try Item.insert { ... }.execute(db)
}
```

### ❌ 忘写 `.execute(db)`
```swift
// 错误 —— 只构建了查询,没有执行
try database.write { db in
    Item.insert { Item.Draft(title: "X") }  // 什么都没发生!
}

// 正确
try database.write { db in
    try Item.insert { Item.Draft(title: "X") }.execute(db)
}
```

## @Table 模型定义

### 基础表

```swift
import SQLiteData

@Table
nonisolated struct Item: Identifiable {
    let id: UUID           // 第一个 `let` = 自动主键
    var title = ""
    var isInStock = true
    var notes = ""
}
```

**要点:**
- 用 `struct` 而非 `class`(值类型)
- Swift 6 并发下加 `nonisolated`
- 第一个 `let` 属性自动成为主键
- 用默认值(`= ""`、`= true`)表示非空列
- 可选属性(`String?`)映射为可空 SQL 列

### 自定义主键

```swift
@Table
nonisolated struct Tag: Hashable, Identifiable {
    @Column(primaryKey: true)
    var title: String      // 自定义主键
    var id: String { title }
}
```

### 列定制

```swift
@Table
nonisolated struct RemindersList: Hashable, Identifiable {
    let id: UUID

    @Column(as: Color.HexRepresentation.self)  // 自定义类型表示
    var color: Color = .blue

    var position = 0
    var title = ""
}
```

### 外键

```swift
@Table
nonisolated struct Reminder: Hashable, Identifiable {
    let id: UUID
    var title = ""
    var remindersListID: RemindersList.ID  // 外键(显式列)
}

@Table
nonisolated struct Attendee: Hashable, Identifiable {
    let id: UUID
    var name = ""
    var syncUpID: SyncUp.ID  // 引用父表
}
```

**注意**:SQLiteData 使用显式外键列。表关系通过 join 表达,而不是 `@Relationship` 宏。关联表 join 查询见 [references/advanced-patterns.md](references/advanced-patterns.md)。

### @Ephemeral —— 非持久化属性

标记存在于 Swift 但不入库的属性:

```swift
@Table
nonisolated struct Item: Identifiable {
    let id: UUID
    var title = ""
    var price: Decimal = 0

    @Ephemeral
    var isSelected = false  // 不入库

    @Ephemeral
    var formattedPrice: String {  // 计算属性,不入库
        "$\(price)"
    }
}
```

**适用场景:**
- UI 状态(选中、展开、悬停)
- 由存储列派生的计算属性
- 业务逻辑的临时标记
- schema 中还没有的属性默认值

**重要**:`@Ephemeral` 属性必须有默认值,因为数据库不会填充它们。

## 技能工作流

### 步骤1:技术选型确认

按「何时选择 SQLiteData」对齐需求,排除应改用 SwiftData 或原生 GRDB 的场景。

### 步骤2:建模与配置

用 `@Table` 定义值类型模型(见「@Table 模型定义」),创建数据库并注册到 Dependencies(完整代码见 [references/advanced-patterns.md](references/advanced-patterns.md) 的「数据库配置」)。

### 步骤3:实现增删改查

用 `database.write { }` 包裹所有写操作,`@FetchAll`/`@FetchOne` 驱动 SwiftUI 刷新(见「查询模式」「增删改」),遵守「常见反模式」。

### 步骤4:按需进入进阶主题

需要 Upsert、关联表 join、`#sql` 原生 SQL、CloudKit 同步或 GRDB 降级时,查阅 [references/advanced-patterns.md](references/advanced-patterns.md)。

## 查询模式

### 属性包装器(@FetchAll、@FetchOne)

SwiftUI 中观察数据库变化的主要方式:

```swift
struct ItemsList: View {
    @FetchAll(Item.order(by: \.title)) var items

    var body: some View {
        List(items) { item in
            Text(item.title)
        }
    }
}
```

**要点:**
- 自动订阅数据库变化
- 任何 `Item` 变化时自动更新
- 在主线程运行
- 视图消失时取消观察(iOS 17+)

### @FetchOne 做聚合

```swift
struct StatsView: View {
    @FetchOne(Item.count()) var totalCount = 0
    @FetchOne(Item.where(\.isInStock).count()) var inStockCount = 0

    var body: some View {
        Text("总数: \(totalCount),有货: \(inStockCount)")
    }
}
```

### 感知生命周期的查询(v1.4.0+)

用 `.task` 在视图消失时自动取消观察:

```swift
struct ItemsList: View {
    @Fetch(Item.all, animation: .default)
    private var items = [Item]()

    @State var searchQuery = ""

    var body: some View {
        List(items) { item in
            Text(item.title)
        }
        .searchable(text: $searchQuery)
        .task(id: searchQuery) {
            // 视图消失或 searchQuery 变化时自动取消
            try? await $items.load(
                Item.where { $0.title.contains(searchQuery) }
                    .order(by: \.title)
            ).task  // ← .task 实现自动取消
        }
    }
}
```

**v1.4.0 之前**(手动清理):
```swift
.task {
    try? await $items.load(query)
}
.onDisappear {
    Task { try await $items.load(Item.none) }
}
```

### 过滤与排序

```swift
// 简单 keypath 过滤
let active = Item.where(\.isActive)

// 复杂闭包过滤
let recent = Item.where { $0.createdAt > lastWeek && !$0.isArchived }

// 包含/前缀/后缀
let matches = Item.where { $0.title.contains("phone") }
let starts = Item.where { $0.title.hasPrefix("iPhone") }

// 排序
let sorted = Item.order(by: \.title)               // 单列
let descending = Item.order { $0.createdAt.desc() } // 降序
let multiSort = Item.order { ($0.priority, $0.createdAt.desc()) } // 多列
```

### 静态查询辅助(v1.4.0+)

```swift
// 旧写法(繁琐)
let items = try Item.all.fetchAll(db)
let item = try Item.find(id).fetchOne(db)  // 返回 Optional<Item>

// 新写法(简洁)
let items = try Item.fetchAll(db)
let item = try Item.find(db, key: id)      // 返回非可选 Item,找不到会抛错

// 也适用于 where 子句
let active = try Item.where(\.isActive).find(db, key: id)
```

## 增删改

### 插入

```swift
try database.write { db in
    try Item.insert {
        Item.Draft(title: "New Item", isInStock: true)
    }
    .execute(db)
}
```

### 用 RETURNING 取生成的主键

```swift
let newId = try database.write { db in
    try Item.insert {
        Item.Draft(title: "New Item")
    }
    .returning(\.id)
    .fetchOne(db)
}
```

### 更新与删除

```swift
// 更新单条
try database.write { db in
    try Item.find(itemId)
        .update { $0.title = "Updated Title" }
        .execute(db)
}

// 批量更新
try database.write { db in
    try Item.where(\.isArchived)
        .update { $0.isDeleted = true }
        .execute(db)
}

// 删除单条
try database.write { db in
    try Item.find(id).delete().execute(db)
}

// 批量删除
try database.write { db in
    try Item.where { $0.createdAt < cutoffDate }
        .delete()
        .execute(db)
}
```

### 事务安全

`database.write { }` 内的所有变更都包在事务里:

```swift
try database.write { db in
    // 要么全部成功,要么全部失败
    try Item.insert { ... }.execute(db)
    try Item.find(id).update { ... }.execute(db)
    try OtherTable.find(otherId).delete().execute(db)
}
```

任一操作抛错,整个事务回滚。

## 进阶主题

以下内容见 [references/advanced-patterns.md](references/advanced-patterns.md):

- 关联表 join 查询(把过滤下推到数据库层)
- Upsert(`ON CONFLICT` 冲突目标、部分唯一索引、合并策略、常见错误)
- 批量插入
- `#sql` 宏的自定义表达式与原生 SQL
- CloudKit 同步(SyncEngine 配置、手动同步、状态观察、同步元数据查询、主键迁移)
- 降级到 GRDB(复杂 join、窗口函数、性能关键路径)
- tvOS 数据持久化建议

## 资源

**GitHub**:pointfreeco/sqlite-data、pointfreeco/swift-structured-queries、groue/GRDB.swift

**目标平台**:iOS 17+,Swift 6
**框架版本**:SQLiteData 1.4+

使用说明

# SQLiteData 类型安全持久化指南

在 Swift 项目中使用 Point-Free 的 SQLiteData 做类型安全 SQLite 持久化:@Table 建模、响应式查询、CRUD/Upsert、#sql 原生 SQL、CloudKit 同步。SwiftData 的高性能替代方案。

## 适用场景

- iOS 17+ / Swift 6 项目需要类型安全的 SQLite 持久化
- 需要 CloudKit 同步与记录共享
- 大数据量(5 万条以上)下追求接近原生 SQLite 的性能
- 评估 SwiftData vs SQLiteData vs GRDB 选型

## 使用方式

对 AI 助手说:

```text
用 SQLiteData 帮我建一个 Item 表模型
这个查询怎么用 @FetchAll 写
帮我把本地数据库接到 CloudKit 同步
这条 SQL 用 #sql 宏怎么写
```

## 核心概念

- 值类型 `struct` + `@Table` 宏 + `database.write { }` 事务闭包
- `@FetchAll` / `@FetchOne` 属性包装器驱动 SwiftUI 自动刷新
- 第一个 `let` 属性自动成为主键;显式外键列 + join 表达关系
- 进阶内容(Upsert、CloudKit、GRDB 降级)见 `references/advanced-patterns.md`

## 环境要求

iOS 17+,Swift 6 严格并发;依赖 SQLiteData 1.4+(基于 GRDB 与 StructuredQueries)。

如何安装此技能?

访问技能市场,点击「安装」按钮,按提示将技能包放入 AI 编程助手的 skills 目录即可。

浏览技能市场

支持平台:Qoder · QoderWork · Claude · Codex 等 AI 编程助手