Sqlable 是一个 Swift 库,可以像馅饼一样轻松地将数据存储在 SQLite 数据库中。创建一个结构体,使其成为 Sqlable,然后尽情地读取、写入、更新和删除数据吧!
在制作 iOS 应用程序时,持久化通常很麻烦。如果您只想使用 Apple 的第一方框架,您的选择要么是 Core Data,要么是序列化为 JSON 或 plist 文件。也有一些不错的第三方选项,例如 Realm,但它主要是活动对象在后台进行大量动态操作。如果您只想操作简单的结构体,并且只想读取和写入数据库,Sqlable 是最简单的选择。
假设您有以下结构体
struct Bicycle {
let id : Int?
var name : String
var color : String
}
并且您想将其持久化到数据库中。为此,您只需通过实现 Sqlable 协议来使您的结构体成为 Sqlable,就像这样
extension Bicycle : Sqlable {
static let id = Column("id", .integer, PrimaryKey(autoincrement: true))
static let name = Column("name", .text)
static let color = Column("color", .text)
static let tableLayout = [id, name, color]
func valueForColumn(column : Column) -> SqlValue? {
switch column {
case Bicycle.id: return id
case Bicycle.name: return name
case Bicycle.color: return color
case _: return nil
}
}
init(row : ReadRow) throws {
id = try row.get(Bicycle.id)
name = try row.get(Bicycle.name)
color = try row.get(Bicycle.color)
}
}
这些是您需要指定的仅有的三件事
• 表格布局(您要存储哪些列)
• 如何保存列
• 如何读取行
完成这些操作后,您就可以开始将您的结构体与 SQLite 一起使用了!
使用它的第一步是设置一个新的 SQLite 数据库并为 bicycle 创建表
db = try SqliteDatabase(filepath: documentsPath() + "/db.sqlite")
try db.createTable(Bicycle.self)
然后您就可以准备好从数据库中写入、读取、更新和删除 bicycle 了!
// Insert bicycle
var bike = Bicycle(id: 1, name: "My bike", color: "Black")
try bike.insert().run(db)
// Read all bicycles
let bicycles = try Bicycle.read().run(db)
// Read some bicycles
let redBikes = try Bicycle.read().filter(Bicycle.color == "red").limit(3).run(db)
// Count all bicycles
let bicycleCount = try Bicycle.count().run(db)
// Update a bicycle
bike.name = "Sportsbike"
try bike.update().run(db)
// Delete bike
try bike.delete().run(db)
现在,您可以通过插入、删除和更新的更新回调非常轻松地观察更改。
db.observe(.insert, on: Bicycle.self) { id in
print("Inserted bicycle \(id)")
}
db.observe(.update, on: Bicycle.self, id: 2) { id in
print("bicycle 2 has been updated")
}
Sqlable 现在使用 SQLite 的预写式日志,这使得并发操作快速而简单。并且由于 Sqlable 使用的是简单的结构体进行操作,因此您可以在不同的线程之间自由传递信息。
let child = try db.createChild()
dispatch_async(background_queue) {
let bicycles = try! Bicycle.read().run(child)
dispatch_async(main_queue) {
self.displayData(bicycles)
}
}
您可以使用事务来锁定数据库,其他线程将等待直到事务完成。
所有公共函数现在都有文档 😎
• 嵌套事务
• 用于过滤器的 SQL 函数调用(例如“like”、“upper”和“lower”)
事务非常容易实现
try db.transaction { db in
if try Bicycle.count().run(db) == 0 {
try bike.insert().run(db)
}
}
您还可以进行回滚
try db.beginTransaction()
try bike.insert().run(db)
try db.rollbackTransaction()
以及嵌套事务!(1.1.1 版本的新功能!)
try db.transaction { db in
try db.transaction { db in
if try Bicycle.count().run(db) == 0 {
try bike.insert().run(db)
}
}
}
extension Bicycle : Sqlable {
static let ownerId = Column("owner_id", .Integer, ForeignKey<Person>())
...
您还可以指定其他列和删除/更新规则
extension Bicycle : Sqlable {
static let ownerId = Column("owner_id", .Integer, ForeignKey<Person>(column: Person.regId, onDelete: .cascade))
...
(1.1 版本的新功能!)
如果您希望数据库中的每个 bicycle 都具有唯一的名称
extension Bicycle : Sqlable {
static let name = Column("name", .text)
static let tableConstraints : [TableConstraint] = [Unique(Bicycle.name)]
...
或者,如果您希望名称和颜色的组合是唯一的
extension Bicycle : Sqlable {
static let name = Column("name", .text)
static let color = Column("color", .text)
static let tableConstraints : [TableConstraint] = [Unique(Bicycle.name, Bicycle.color)]
...
Bicycle.read().filter(Bicycle.color == "red" && !(Bicycle.id == 0 || Bicycle.id > 1000))
(1.2 版本的新功能!)
在您的数据库处理程序上注册 didUpdate
回调,以便在任何内容发生更改时收到通知
db.didUpdate = { table, id, change in
switch change {
case .insert: print("Inserted \(id) into \(table)")
case .update: print("Updated \(id) in \(table)")
case .delete: print("Deleted \(id) from \(table)")
}
}
但更好的是,您可以注册针对特定操作、表和 ID 的事件回调
db.observe(.insert, on: Bicycle.self) { id in
print("Inserted bicycle \(id)")
}
db.observe(.update, on: Bicycle.self, id: 2) { id in
print("bicycle 2 has been updated")
}
db.observe(.delete, on: Bicycle.self) { id in
print("bicycle \(id) has been deleted")
}
db.observe(on: Bicycle.self) { id in
print("Something was updated on bicycle \(id)")
}
每个可能失败的函数调用都标有 throws,因此您可以处理每个可能发生的错误。
API 的构造方式也使您可以在不接触数据库的情况下设置语句,因此您可以设置所有内容而无需任何错误处理
let idFilter = Bicycle.id > 2 && Bicyckle.id < 10
let read = Bicycle.read().filter(idFilter).limit(2)
do {
let result = read.run(db)
} catch let error {
// handle error
}
它还支持一个可选的方便回调,用于在发生任何错误时使用,如果它适合您的应用程序,您可以使用它。只需在您的数据库处理程序上调用 fail
方法,当您遇到如下错误时
do {
try Bicycle.read().limit(-1).run(db)
} catch let error {
db.fail(error)
}
它将被传递到您注册的错误处理程序
db.didFail = { error in
print("Oh no! \(error)")
}
(1.2 版本的新功能!)
曾经处理过 Core Data 中的并发问题吗?不用担心,Sqlable 与之完全不同。
每个 SqliteDatabase 实例对于一个线程/队列都是唯一的。如果您想在后台运行一些代码,您可以创建一个子实例
let child = try db.createChild()
dispatch_async(background_queue) {
for bicycle in try! Bicycle.read().run(child) {
...
}
}
这是唯一的规则:保持每个实例在其线程/队列上是唯一的。Sqlable 和 Sqlite 将负责正确地锁定数据库。父实例甚至会收到来自子实例中所做更改的更新通知。如果您希望某些数据库操作按顺序发生,您可以将它们放在一个事务中,这将自动锁定所有线程上的数据库。
此外,由于模型实例只是值类型,因此您可以在线程上自由移动它们。如果您需要从数据库中加载 10,000,000 个 bicycle,只需在后台运行读取查询,并将结果分派到主队列。
如果您正在使用 Carthage(您应该这样做!),只需将此添加到您的 Cartfile
github "ulrikdamm/Sqlable"
然后在您的源文件中
import Sqlable
您就可以开始了!
• 迁移
• 连接
当您使一个结构体成为 Sqlable 时,它会获得实例方法和静态方法来返回语句。这些方法是 read
、count
、insert
、update
和 delete
。所有这些都返回一个 Statement 结构体,您可以对其进行修改(使用 .filter、.limit、.onConflict、.orderBy)。语句只是一个不可变的结构体,没有任何魔法。您可以保存它、序列化它等等。当您想要运行语句时,只需调用 run 方法,该方法接受一个数据库处理程序来在其中运行它,并且可能会抛出错误,或者给您一个结果。结果的类型取决于创建语句的初始方法。
查询 DSL 支持以下运算符
等于:column == value (例如 Bicycle.id == 1)
不等于:column != value
小于:column < value
小于或等于:column <= value
大于:column > value
大于或等于:column => value
与:expression && expression
或:expression || expression
反转:!expression
为空:!column
包含:column ∈ [value]
或 contains(column, value)
字符串小写:column.lowercase()
字符串大写:column.uppercase()
字符串内搜索:column.like(value)
(例如 Bicycle.name.like("%bike%"))
column
表示 Column 结构体的实例,例如 Bicycle.id
。
value
表示任何适用于 SQL 的值,例如 Int、String、Double 等。
expression
是这些运算符之一返回的任何内容
我做的。我的名字是 Ulrik Flænø Damm,我是哥本哈根 Northplay 的 iOS 和 Unity 开发人员。您可以在 Twitter 上关注我,或访问 我的网站。
如果您想贡献代码或想法,请打开一些 issues 或提交一些 pull requests!