前言
移动开发经常需要使用 sqlite,对于稍微复杂一点的存储 FMDB 是首选。 一般我们都会对FMDB再进行一个封装使其更适合自己工程的使用。这是我用代码一步一步实现自己的 ios 架构一部分。fmdb 没有swift 版本。但是任然可用。我使用的是 FMDB v2.7.5 具体如何引入工程参考 GitHub。
问题
既然要封装FMDB 我就要看看我们要解决哪些问题
- 业务逻辑需求 (选择多数据库)
- 数据效率 (多线程)
- 数据安全 (FMDatabaseQueue)
- 数据一致性 (transaction)
- 调用方便
- 数据库升级
选择多数据库模式
对于拥有游客和用户类型的App,数据有两种选择:
单个库,多张表 | 每张表都有一个UserId字段区分,表比较大,sql 相对麻烦 ,使用简单 |
多库多表 | 通过库区分不同用户,表小,效率更高,使用相对复杂 |
以前看过微信的数据库好像是多库的。
我这里选择 多库模式
ATTACH DATABASE 'dbPath' as 'dbName'
DETACH DATABASE 'dbName'
- 主要用这两个语句创建/分离数据库。注意
dbPath
上的引号不要遗漏。 - 默认是游客数据库id为0,user 登录成功就 attach 到user 数据库
- 因为切换用户要 diattach ,一次程序启动只有 游客和某一个具体的数据库可用。
- 登录用户可以看游客的内容,但游客只能能查看部分登录用户页面。所以不同角色拥有的数据库的表并不相同。
- 我这里并没有联库查询,那样太复杂。相同的角色拥有相同的表。
简单的分析我们可以看出,需要分角色,不同角色看的页面不同,表不同。对应的枚举是
enum ResAccessRole: String {
case Public = "pub"
case User = "user"
case All = "all"
}
这里简单点就是登录用户,游客(public),为了代码方便实现有一个All。dbName 就是对应的角色。
- 更详细的用户切换,我在登陆模块说明。
效率和安全性
关于安全性 FMDB 已经考虑了 ,但是效率问题,需要我们自己管理
FMDatabaseQueue 的内部使用的是 串行队列
同步任务
保证了是线程的安全性,
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_sync(_queue, ^() {
//相关代码
}
- 数据库的增删改查我这里都使用回调的方式,大部分时候顺序并不重要。
- 数据库的操作不要阻塞主线程,也不能能因为某个操作耗时,阻塞了其他任务。
- 有些操作必须先完成比如创建数据库,数据库升级。
- 综上:只要我们使用 FMDatabaseQueue 就一定是安全的但是这个方法会阻塞当前线程。我们要封装一下。我用一个并行队列+异步任务 + banner 达成我的目的。 这个方法的高效可以看看 Objective-C、Swift、java、Dart(Flutter)、NodeJs、Js、Python... 我该如何选择?焦虑!的NOdeJS部分。gcd的使用参考iOS 多线程:『GCD』详尽总结 『不羁阁』『行走少年郎』
数据一致性 transaction
参考我对SQLite的强行研究
这里我主要用这个方法
- (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
数据库升级
数据库的升级由数据库模块完成大部分常规的升级,比如增加字段。并放出接口有各个模块实现其特殊的需求。
DBModelVersion 保存每个model 的数据库版本
truct DBModelVersion: HandyJSON {
var name :String!
var version :Int!
}
extension DBModelVersion: DBModel {
static var dbPrimaryKeys: [String]? { return ["name"] }
static var dbTableName: String {
return "dbversion"
}
static var dbColumns: Dictionary<String, DBConstants.DataType> {
return [
"name" : .text,
"version" : .integer
]
}
}
对比 version 看是否升级数据库
调用方便
关于 sql 语句的书写我封装了方法 参考sql 语句Swift封装,链式调用
代码实现
基本思路说完了,下面看看具体的代码。代码看源码都很简单,我说说一些主要点。
主要类
- DataBaseModule 实现了 ModuleProtocl 主要在数据库模块加载时完成数据库相关设置:配置、创建、升级等。 configDB,clearDBCache,createDB这个几个方法需要banner模式。
- DBHelper 数据库操作的具体实现。其他类都是调用这个类里面具体实现。比如:增删改查等。提供了统一的 并行队列:executionQueue。
- BaseDao 其他所有模块类的基类比如 USerDao。提供一些基本信息dbName,将回调返回到主线程
- SqlStatement sql 语句封装 便于使用
- DBProtocol.swift 定义了要实现数据库功能的必要的协议
- DBModelVersion 数据表版本数据结构
一些我认为的知识点
- 定义常量 :struct DBConstants 层次清楚
- 封装一些容易遗漏的代码 extension FMResultSet 代码更适合使用
- 模板方法 func queryModel<T: Model>。 oc 很少使用,swift 很好用
- 注意 可选型的非逃逸闭包, 我写的时候不清楚,总是提示使用不对
看源码
TODO:关于数据库操作的顺序可以分得更细。哪些可以并行,哪些必须安顺序进行。但这个工程比较大。我目前的方案效率对我来说够了。