SQLite.swift 使用与封装
https://github.com/stephencelis/SQLite.swift SQLite.swift的Github地址,现在有5千多个star,是swift语言中关于sqlite星数最多的框架,值得推荐。
SQLite.swift对SQLite进行了全面的封装,拥有全面的纯swift接口,即使你不会SQL语句,也可以使用数据库。作者采用了链式编程的写法,让数据库的管理变得优雅,可读性也很强。话不多说,我们直接开始用起来。
-
集成:
1. Carthage:
github "stephencelis/SQLite.swift"
2. CocoaPods:
pod 'SQLite.swift'
3. Swift Package Manager:
dependencies: [
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5")
]
4. 手动集成:
从GitHub下载工程,把SQLite.xcodeproj
拖到工程里,然后在 Linked Frameworks and Libraries.
里添加SQLite.framework
就可以了。
-
使用:
1. 创建数据库
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
let db = try! Connection("\(path)/db.sqlite3")
这里我们设置好数据库文件的路径和名称,作为参数初始化一个Connection
对象就可以了,如果路径下文件不存在的话,会自动创建。
- 初始化方法:
这只是最简单的方式,我们来深入看一下Connection
的初始化方法和可以设置的参数:
public init(_ location: SQLite.Connection.Location = default, readonly: Bool = default) throws
第一个参数Location
指的是数据库的位置,有三种情况:
inMemory
数据库存在内存里;temporary
临时数据库,使用完会被释放掉;filename (or path)
存在硬盘中,我们上面用的就是这种。前两种使用完毕会被释放不会保存,第三种可以保存下来;第一种数据库存在内存中,后两种存在硬盘里。
readonly
数据库是否为只读不可修改,默认为false。只读的情况一般是我们复制一个数据库文件到我们的项目,只读取数据使用,不做修改。 - 线程安全设置:
使用数据库避免不了多线程操作,SQLite.swift中我们有两个选项可以设置
db.busyTimeout = 5.0
db.busyHandler({ tries in
if tries >= 5 {
return false
}
return true
})
在框架GitHub主页的Issues里看到有人说这两个不用同时设置,设置一个就好了。Generally in SQLite, busy timeouts are a kind of busy handler. So you have to chose: set a busy timeout, or set a custom busy handler. But you can't do both.
2. 创建表
let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")
try db.run(users.create(temporary: false, ifNotExists: true, withoutRowid: false, block: { (t) in
t.column(id, primaryKey: true)
t.column(name)
t.column(email, unique: true)
})
)
这里就看到一个SQLite.swift
特别坑的地方了,他的数据存储和读取,全部是用Expression
。
- temporary:是否是临时表
- ifNotExists:是否不存在的情况才会创建,记得设置为true
- withoutRowid: 是否自动创建自增的rowid
3. 增
let insert = users.insert(name <- "Alice", email <- "alice@mac.com")
if let rowId = try? db.run(insert) {
print("插入成功:\(rowId)")
} else {
print("插入失败")
}
// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com')
插入成功会返回对应的rowid
4. 删
let alice = users.filter(id == rowid)
if let count = try? db.run(alice.delete()) {
print("删除的条数为:\(count)")
} else {
print("删除失败")
}
删除成功会返回删除的行数int值
5. 改
let alice = users.filter(id == rowid)
if let count = try? db.run(alice. update()) {
print("修改的条数为:\(count)")
} else {
print("修改失败")
}
改跟删除类似,返回的是修改了的行数
6. 查
let query = users.filter(name == "Alice").select(email).order(id.desc).limit(l, offset: 1)
for user in try db.prepare(query) {
print("email: \(user[email])")
//email: alice@mac.com
}
这里我写了一个复杂的条件搜索,加了排序、条数、偏移量等参数,看得懂了差不多就一法通万法了。
使用数据库的另一个优势就是可以帮我们做一些以前需要遍历才能完成的计算,比如计算所有人的某课成绩的总值、平均值等等,我这里简单举个例子:
//score : Expression<Int64>("score") 成绩的列,int类型
for result in try db.prepare(users.select(score.sum, score.average)) {
let sum = result[score.sum] //总成绩
let average = result[score.average] //评价成绩
}
-
封装:
建议采用单例封装,由于存取列表元素要用Expression,所以要根据自己项目的字段设置几个全局变量来使用,下面来看一个我根据具体项目的封装:
//
// SQLiteManager.swift
// Copyright © 2018年 Aaron Feng. All rights reserved.
//
import UIKit
import SQLite
import SwiftyJSON
let type_column = Expression<Int>("type")
let time_column = Expression<Int>("time")
let year_column = Expression<Int>("year")
let month_column = Expression<Int>("month")
let week_column = Expression<Int>("week")
let day_column = Expression<Int>("day")
let value_column = Expression<Double>("value")
let tag_column = Expression<String>("tag")
let detail_column = Expression<String>("detail")
let id_column = rowid
class SQLiteManager: NSObject {
static let manager = SQLiteManager()
private var db: Connection?
private var table: Table?
func getDB() -> Connection {
if db == nil {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
db = try! Connection("\(path)/db.sqlite3")
db?.busyTimeout = 5.0
}
return db!
}
func getTable() -> Table {
if table == nil {
table = Table("records")
try! getDB().run(
table!.create(temporary: false, ifNotExists: true, withoutRowid: false, block: { (builder) in
builder.column(type_column)
builder.column(time_column)
builder.column(year_column)
builder.column(month_column)
builder.column(week_column)
builder.column(day_column)
builder.column(value_column)
builder.column(tag_column)
builder.column(detail_column)
})
)
}
return table!
}
//增
func insert(item: JSON) {
let insert = getTable().insert(type_column <- item["type"].intValue, time_column <- item["time"].intValue, value_column <- item["value"].doubleValue, tag_column <- item["tag"].stringValue , detail_column <- item["detail"].stringValue, year_column <- item["year"].intValue, month_column <- item["month"].intValue, week_column <- item["week"].intValue, day_column <- item["day"].intValue)
if let rowId = try? getDB().run(insert) {
print_debug("插入成功:\(rowId)")
} else {
print_debug("插入失败")
}
}
//删单条
func delete(id: Int64) {
delete(filter: rowid == id)
}
//根据条件删除
func delete(filter: Expression<Bool>? = nil) {
var query = getTable()
if let f = filter {
query = query.filter(f)
}
if let count = try? getDB().run(query.delete()) {
print_debug("删除的条数为:\(count)")
} else {
print_debug("删除失败")
}
}
//改
func update(id: Int64, item: JSON) {
let update = getTable().filter(rowid == id)
if let count = try? getDB().run(update.update(value_column <- item["value"].doubleValue, tag_column <- item["tag"].stringValue , detail_column <- item["detail"].stringValue)) {
print_debug("修改的结果为:\(count == 1)")
} else {
print_debug("修改失败")
}
}
//查
func search(filter: Expression<Bool>? = nil, select: [Expressible] = [rowid, type_column, time_column, value_column, tag_column, detail_column], order: [Expressible] = [time_column.desc], limit: Int? = nil, offset: Int? = nil) -> [Row] {
var query = getTable().select(select).order(order)
if let f = filter {
query = query.filter(f)
}
if let l = limit {
if let o = offset{
query = query.limit(l, offset: o)
}else {
query = query.limit(l)
}
}
let result = try! getDB().prepare(query)
return Array(result)
}
}
这里我使用了SwiftyJSON
这个框架来传值,print_debug
是一个自定义的只在调试状态输出的函数,读我代码的时候不用纠结这些。这里查询完毕,我把结构变成包装成一个数组返回,下面看一下我在工程里的使用的情况:
let inV = SQLiteManager.manager.search(filter: year_column == year && month_column == month && type_column == 1,
select: [value_column.sum]).first?[value_column.sum] ?? 0.0
//计算year年month月type为1的所有value的和
这里就是做了一个比较复杂的sql计算,如果正常用代码遍历的话要写一大堆,速度也慢,数据库的优势比较明显。
这里的封装是我针对自己项目的封装,用到的变量也是比较有针对性,大家封装的时候要根据自己工程的情况来写,这里贴出来是为了给大家提供一下思路,我的封装代码里有很多值得推敲的点,比如查询条件的默认值的设置、update的写死的列,这些都是针对具体项目的设置,看懂的话也许大家能猜出我当初这么写的用意。
欢迎更正错误和交流,回复评论和私信皆可 😊