iOS Swift 4.0:第三方SQLite框架 SQLite.swift 使用(一)

最近在看Swift 4.0,看到SQLite部分,回想起之前在Swift下使用SQLite很是麻烦(当然OC下也是挺麻烦的),苹果官方文档中,要使用原生的SQLite,要先导入framwork,然后建一个.h文件,还要做一次Swift与C语言的桥接(SQLite是基于C语言的),使用的时候还要写SQL语句,反正就是烦!!!
然后Swift下的SQLite.swift应运而生,我自己也用了一下,也是挺好用,所以特地在此总结一下这个框架的使用。

一、使用cocoaPods导入
不会使用cocoaPods请自行解决啦嘻嘻。在Podfile里面加上一句

pod 'SQLite.swift', '~> 0.11.4'

然后 pod install 一下就好了

二、开始使用
(一)导入:在要使用SQLite的地方导入头文件,当然一般会将数据库操作封装到一个类中

import SQLite

(二)创建/连接数据库
比如在iOS下,要在Document文件夹下创建一个名为db.sqlite3的数据库,并且要连接数据库

//获取doc路径
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
//如果不存在的话,创建一个名为db.sqlite3的数据库,并且连接数据库
let db = try Connection("\(path)/db.sqlite3")

如果你把一个数据库应用程序(例如,复制数据库文件到你的Xcode项目并将其添加到你的应用程序的target),你可以建立一个只读连接。

let path = Bundle.main.pathForResource("db", ofType: "sqlite3")
let db = try Connection(path, readonly: true)

如果省略路径,sqlite.swift将提供一个内存数据库。

//创建一个内存数据库
let db = try Connection() 

如果要在temporary创建临时磁盘备份数据库,使用如下格式,数据库会在连接关闭后自动删除

let db = try Connection(.temporary)

有时候多个数据库连接使用同一个数据库的时候,可能会出现线程安全的问题,这时候访问数据库可能会出错,这时候就有类似于网络访问超时的机制,如果为单个数据库维护多个连接,可以设置超时时间(以秒为单位)以及繁忙处理程序。

db.busyTimeout = 5

db.busyHandler({ tries in
    if tries >= 3 {
        return false
    }
    return true
})

(三)基本表达式
下图是Swift和SQLite数据类型的对照图


类型对照图.jpeg

表达式是与类型(内置或自定义)、原始SQL和(可选)值绑定到该SQL的通用结构。通常,您只需显式地创建表达式来描述列,通常每列只有一次。例如:

let id = Expression<Int64>("id")
let email = Expression<String>("email")
let balance = Expression<Double>("balance")
let verified = Expression<Bool>("verified")

以上表达式分别表示表的字段,第一个字段名是id,类型是Int64,第二个字段名是email,类型是String...以此类推,以上创建的是不为空的,要创建可以为空的字段,如下:

let name = Expression<String?>("name")

(四)创建表
声明一个Table常量

let users = Table("users")

使用上文表达式创建的字段,如果表不存在,就创建表

try db.run(users.create(ifNotExists: true) { t in     // CREATE TABLE "users" (
    t.column(id, primaryKey: true) //     "id" INTEGER PRIMARY KEY NOT NULL,
    t.column(email, unique: true)  //     "email" TEXT UNIQUE NOT NULL,
    t.column(name)                 //     "name" TEXT
}) 

我们也可以对字段进行约束。设置字段为主键/自增/唯一/检查/设置默认值/校对/设置外键等等

t.column(id, primaryKey: true)
// "id" INTEGER PRIMARY KEY NOT NULL

t.column(id, primaryKey: .autoincrement)
// "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL

t.column(email, unique: true)
// "email" TEXT UNIQUE NOT NULL

t.column(email, check: email.like("%@%"))
// "email" TEXT NOT NULL CHECK ("email" LIKE '%@%')

t.column(name, defaultValue: "Anonymous")
// "name" TEXT DEFAULT 'Anonymous'

t.column(email, collate: .nocase)
// "email" TEXT NOT NULL COLLATE "NOCASE"
t.column(name, collate: .rtrim)
// "name" TEXT COLLATE "RTRIM"

t.column(user_id, references: users, id)
// "user_id" INTEGER REFERENCES "users" ("id")

当然,除了字段约束,还有表之间的约束,可能会有点懵,不太懂得请去复习一下数据库相关的知识,有数据库基础看这些简直没有压力哈哈。

 //不像上面的字段约束,它支持所有的数据库类型,升序和降序命令,和复合(多字段)键。
 t.primaryKey(email.asc, name)
// PRIMARY KEY("email" ASC, "name")

//不像上面的字段约束,它支持复合(多字段)键。
t.unique(local, domain)
// UNIQUE("local", "domain")

t.check(balance >= 0)
// CHECK ("balance" >= 0.0)

//可以使用过滤器运算符和函数轻松地构造布尔表达式。
t.check(balance >= 0)
// CHECK ("balance" >= 0.0)

//和上面的引用约束不同的是,它支持SQLite的全部类型,比如更新、删除操作以及复合(多字段)键。
t.foreignKey(user_id, references: users, id, delete: .setNull)
// FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL

(五)插入操作
通过insert()方法进行插入操作,操作成功会返回一个类型为Int64的ROWID

try db.run(users.insert(email <- "alice@mac.com", name <- "Alice"))
// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice')

try db.run(users.insert(or: .replace, email <- "alice@mac.com", name <- "Alice B."))
// INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.')

do {
    let rowid = try db.run(users.insert(email <- "alice@mac.com"))
    print("inserted id: \(rowid)")
} catch {
    print("insertion failed: \(error)")
}

如果insert()方法没有任何参数,全部字段都会以默认值录入数据库表,字段没有默认值的,会报错。

(六)错误处理
你可以匹配上选择性地抓住SQLite的错误错误。例如,专门处理约束错误

do {
    try db.run(users.insert(email <- "alice@mac.com"))
    try db.run(users.insert(email <- "alice@mac.com"))
} catch let Result.error(message, code, statement) where code == SQLITE_CONSTRAINT {
    print("constraint failed: \(message), in \(statement)")
} catch let error {
    print("insertion failed: \(error)")
}

Result.error类型包含描述错误的英语文本(信息),错误代码(参见SQLite结果代码列表详情),产生错误的语句的一个可选的引用。

(七)赋值方法
在SQLite.swift中,可以直接使用 '<-' 为字段赋值,由上文表达式一节可见端倪,同样,我们也可以在update,delete等语句中使用同样格式进行操作。比如,在更新语句中:

try db.run(users.update(balance-=10))

以上语句是指对表中全部数据的balance字段进行 -10 操作

(八)查询操作
在SQLite.swift中,使用prepare()方法表示SELECT操作。此方法返回的是一组数据,我们可以使用forin循环从里面取出数据

for user in try db.prepare(users) {
    print("id: \(user[id]), email: \(user[email]), name: \(user[name])")
}
// SELECT * FROM "users"

在一条数据中的列下标将尝试中止错误的情况下执行,如果想处理这种情况,可以使用Row.get(_ column: Expression<V>)

for user in try db.prepare(users) {
    do {
        print("name: \(try user.get(name))")
    } catch {
        // handle
    }
}    

我们可以通过对数据库连接上的pluck方法的查询来获取第一行。

if let user = try db.pluck(users) { /* ... */ } // Row
// SELECT * FROM "users" LIMIT 1

将查询结果封装成一个数组也很简单,我们可以这样处理:

let all = Array(try db.prepare(users))
// SELECT * FROM "users"

上面的是指一些很简单的查询操作,实际操作中这些肯定满足不了我们的需求的,所以要用到复合查询操作。

let query = users.select(email)           // SELECT "email" FROM "users"
                 .filter(name != nil)                // WHERE "name" IS NOT NULL
                 .order(email.desc, name)   // ORDER BY "email" DESC, "name"
                 .limit(5, offset: 1)                 // LIMIT 5 OFFSET 1

选择指定的字段

for user in try db.prepare(users.select(id, email)) {
   print("id: \(user[id]), email: \(user[email])")
    // id: 1, email: alice@mac.com
}
// SELECT "id", "email" FROM "users"

数据表内联接,比如将posts表的user_id和users表的id作为连接条件,内联两个表

users.join(posts, on: user_id == users[id])
// SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id")

以上是表之间的连接,有时候涉及到表内自联,这时候就要用到数据表别名alias

let managers = users.alias("managers")
let query = users.join(managers, on: managers[id] == users[managerId])
// SELECT * FROM "users"
// INNER JOIN ("users") AS "managers" ON ("managers"."id" = "users"."manager_id")

选择的条件:

users.filter(id == 1)
// SELECT * FROM "users" WHERE ("id" = 1)

users.filter([1, 2, 3, 4, 5].contains(id))
// SELECT * FROM "users" WHERE ("id" IN (1, 2, 3, 4, 5))

users.filter(email.like("%@mac.com"))
// SELECT * FROM "users" WHERE ("email" LIKE '%@mac.com')

users.filter(verified && name.lowercaseString == "alice")
// SELECT * FROM "users" WHERE ("verified" AND (lower("name") == 'alice'))

users.filter(verified || balance >= 10_000)
// SELECT * FROM "users" WHERE ("verified" OR ("balance" >= 10000.0))

排序操作(ASC:升序,DESC:降序),根据email和name字段排序

users.order(email, name)
// SELECT * FROM "users" ORDER BY "email", "name"

email字段降序,name字段升序

users.order(email.desc, name.asc)
// SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC

分页操作:
取前面5条数据

users.limit(5)
// SELECT * FROM "users" LIMIT 5

偏移量为5,取偏移量后面5条数据

users.limit(5, offset: 5)
// SELECT * FROM "users" LIMIT 5 OFFSET 5

聚合操作

let count = try db.scalar(users.count)
  // SELECT count(*) FROM "users"
let count = try db.scalar(users.filter(name != nil).count)
// SELECT count(*) FROM "users" WHERE "name" IS NOT NULL
let count = try db.scalar(users.select(name.count)) // -> Int
// SELECT count("name") FROM "users"
let max = try db.scalar(users.select(id.max)) // -> Int64?
// SELECT max("id") FROM "users"
let min = try db.scalar(users.select(id.min)) // -> Int64?
// SELECT min("id") FROM "users"
let average = try db.scalar(users.select(balance.average)) // -> Double?
// SELECT avg("balance") FROM "users"
let sum = try db.scalar(users.select(balance.sum)) // -> Double?
// SELECT sum("balance") FROM "users"
let total = try db.scalar(users.select(balance.total)) // -> Double
// SELECT total("balance") FROM "users"

(八)更新操作
使用update()方法进行更新操作:

try db.run(users.update(email <- "alice@me.com"))
// UPDATE "users" SET "email" = 'alice@me.com'    

可以先使用filter()方法进行过滤操作,指定范围进行更新操作

let alice = users.filter(id == 1)
try db.run(alice.update(email <- "alice@me.com"))
// UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1)

更新操作会返回被更新数据的行号

do {
    if try db.run(alice.update(email <- "alice@me.com")) > 0 {
        print("updated alice")
    } else {
        print("alice not found")
    }    
} catch {
    print("update failed: \(error)")
}

(九)删除操作
使用delete()方法进行删除操作:

try db.run(users.delete())
// DELETE FROM "users"

可以先使用filter()方法进行过滤操作,指定范围进行删除操作

let alice = users.filter(id == 1)
try db.run(alice.delete())
// DELETE FROM "users" WHERE ("id" = 1)    

删除操作会返回被删除数据的行号

do {
    if try db.run(alice.delete()) > 0 {
        print("deleted alice")
    } else {
       print("alice not found")
    }
} catch {
    print("delete failed: \(error)")
}

好了,今天就先到此为止吧,有点累了哈哈,还有一些内容会后续补上的。
之前我的笔记都是记在印象笔记上面的,这是我第二次写博客,如果有什么错漏的,欢迎交流指正哈~~
后续也会将印象笔记里面的发上来的,谢谢大家O(∩_∩)O~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容