这篇笔记主要是介绍 GRDB 的基本使用(增删改查)。主要是感觉官方的文档有点杂乱,这篇笔记从入门使用者角度看起来更简单容易上手。
GRDB 是操作 SQLite 数据库的工具,特点是对多线程操作的应用提供了比较友好的支持,提供了比较方便的 API 的支持,很多时候是不需要写 SQL 语句的,也不用操心底层操作。
# 建立连接
GRDB 提供了两种访问 SQLite 数据库的方式 DatabaseQueue
和 DatabasePool
,我们可以从中选择一种方式
import GRDB
// Pick one:
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
let dbPool = try DatabasePool(path: "/path/to/database.sqlite")
两者的区别在于
DatabasePool
允许并行的数据库访问,同时是通过 WAL mode 来打开数据库的DatabaseQueue
支持内存数据库
如果不确定的话可以先使用 DatabaseQueue
这种方式,后续可以再切到 DatabasePool
。接下来演示使用 DatabaseQueue
这种方式
DatabaseQueue
建立连接的方式
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
//数据库连接配置
var config = Configuration.init()
config.readonly = false
//初始化 DatabaseQueue 实例
let dbQueue = try DatabaseQueue(path: path, configuration: config)
DatabaseQueue 初始化的 API 里,如果 path 传 nil,则是在内存中创建数据库
有了数据库连接实例之后,我们开始对数据库进行操作。
# 操作
假设我们要保存一份玩家的数据在本地,需要存储玩家的姓名和分数,并根据需要更新玩家表。
# 建表
进行建表操作
try dbQueue.write({ db in
try db.create(table: "player", body: { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("score", .integer).notNull()
})
})
对上面代码进行简单说明
func write<T>( updates: (Database) throws -> T) throws -> T
API (opens new window) 是用来执行数据操作,并在执行完之后返回结果。- 我们使用
Database
类型的实例进行建表操作,对应 API (opens new window)create(table:options:body:)
,在闭包里返回TableDefinition
参数。 - 我们使用
TableDefinition
实例来对数据库表进行配置,定义自增主键id
,添加数据库表列func column(_ name: String,_ type: Database.ColumnType? = nil) -> ColumnDefinition
通过拿到列定义,来对数据库列进行限制。
# 插入数据
代码如下
struct Player: Codable, FetchableRecord, PersistableRecord {
var id: Int64
var name: String
var score: Int
}
try dbQueue.write { db in
try Player(id: 1, name: "Arthur", score: 100).insert(db)
try Player(id: 2, name: "Barbara", score: 1000).insert(db)
}
代码说明
Player
遵守Codable
(opens new window),FetchableRecord
(opens new window),PersistableRecord
(opens new window) 协议。PersistableRecord
是一种可以被持久化到数据中的类型,FetchableRecord
(opens new window) 是可以把从数据库行中解出数据的类型,为了遵守前者需要提供encode(to:)
(opens new window) 的实现,后者需要提供init(row:)
的实现。两者都有默认的实现所以遵守Codable
协议就好了。PersistableRecord
提供了insert
方法,所以可以调用 Player 的 insert 插入到 db 中。- 关于 Codable 的详细说明,我自己的另外一篇博客有介绍 iOS开发中JSON-Model转化 (opens new window)
# 查询数据
代码如下
func queryPlayerDB(with name:String) throws -> Set<Player>? {
let players = try dbQueue?.read { db in
let sql = "SELECT * FROM player WHERE name = ?"
return try Player.fetchSet(db, sql: sql, arguments: [name])
}
return players
}
代码说明
- 和之前的 dbQueue.write 不一样,这里使用读 API func read(_ value: (Database) throws -> T) throws -> T (opens new window),执行只读操作,在执行完成后返回读取结果。
- Player 数据结构遵守了
FetchableRecord
协议,直接调用 fetchSet 方法,传入 Sql 语句参数,系统就直接把查询的结果序列化好返回来了。 - 查询这儿还有很多别的相关的 API,比如
fetchAll(SELECT * FROM player)
,fetchOne(SELECT * FROM player LIMIT 1)
等 .. 可以根据需要来使用
# 修改数据
代码如下
let sql = "SELECT * FROM player WHERE name = ?"
var player = try dbQueue?.read { db in
return try Player.fetchOne(db, sql: sql, arguments: [name])
}
player?.score = 2000
try dbQueue?.write({ db in
try player?.saved(db)
})
代码说明
- 修改数据步骤 ① 查出要修改的数据 ② 修改查出来的数据 ③ 保存数据
- 保存数据使用的 API 是 save(_:onConflict:) (opens new window),这个 API 是执行 insert 和 update 语句。
- 还有一种更加简洁的方法如下,使用的 update(_:onConflict:) (opens new window) API.
try dbQueue.write { db in
if var player = Player.fetchOne(db, id: 1) {
player.score += 10
try player.update(db)
}
}
# 删除数据
代码如下
let player = try dbQueue?.read({ db in
let sql = "select * from player where name = ?"
return try Player.fetchOne(db, sql: sql, arguments: ["Barbara"])
})
try dbQueue?.write({ db in
try player?.delete(db)
})
代码说明
- 查询出来要删除的数据
- 调用 func delete(_ db: Database) throws -> Bool (opens new window) API 来对实例进行删除。
以上是入门级别的使用,相对于 FMDB 来说少了很多不必要的代码。但是并没有深入到更高级的并行操作的支持。
参考地址: