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)。支持平台:Qoder · QoderWork · Claude · Codex 等 AI 编程助手