最近公司任务不重, 有时间将自己之前所用到的知识总结下. 码农总结, 没有比写个Demo更直接的了.就花了些时间写了关于sqlite3的Demo.
性格直爽不想看废话的同学通道 ->
- 直接甩源码: RTDatabase
- 也上传到了cocoapods:
pod 'RTDatabase'
(滑稽)-> 大概上是不能放在项目直接用的, 还没来得及做各种测试.
特别重要:
iOS的sqlite自然就绕不开要参考FMDB.
膜拜感谢!
用法:
RTDatabase 的用法在 readme 中写的还算详细. 本着简单实用,傻瓜式使用的模式设计了接口.
主要有三个类用于操作:
RTDB
RTNext
RTDBDefault
RTDB RTNext
一般的, RTDB RTNext 要联合使用.RTDB 执行 sql 语句, RTNext 获取结果.
- 开启数据库
// Open DB
// Open DB
NSError *err;
BOOL result = [db openWithPath:@"~/RTDB.sqlite3" withError:&err];
if (!result) {
NSLog(@"%@". err);
}
这个方法默认了 sqlite3_open_v2 第三个参数 flags 为:
SQLITE_OPEN_CREATE |
SQLITE_OPEN_READWRITE |
SQLITE_OPEN_FULLMUTEX |
SQLITE_OPEN_SHAREDCACHE
- 建表
建表的过程是:
- sqlite3_open_v2
- sqlite3_step
- sqlite3_finalize
RTDB 提供了三个不同参数的方法, 用于执行这样的过程. 返回值为BOOL, 同时这三个方法都有对应的可以传出error信息的方法.
当 sql 不包含SELECT
时, 执行过程多是以上三步, 推荐使用方法0-0
中的三个方法.
//方法: 0-0
- (BOOL)execQuery:(NSString *)sql, ... NS_REQUIRES_NIL_TERMINATION;
- (BOOL)exceQuery:(NSString *)sql withArrArgs:(NSArray *)arrArgs;
- (BOOL)exceQuery:(NSString *)sql withParams:(NSDictionary *)params;
- 插入数据 更新数据
插入一条数据和更新一条数据的过程大体上是差多不的. 插入数据的过程:
- sqlite3_open_v2
- sqlite3_bind_** // ** 表示数据类型.
- sqlite3_step
- sqlite3_finalize
其中 sqlite3_bind_** 的执行次数与当前插入数据的字段数相同.
- 删除数据
删除一条数据的过程
- sqlite3_open_v2
- sqlite3_step
- sqlite3_finalize
* 建表, 插入, 更新, 删除都是每次只需要执行一次sqlite3_step的操作.
- 查询
查询数据的过程
- sqlite3_open_v2
- sqlite3_step
- sqlite3_column_**
- sqlite3_finalize 其中, sqlite3_step的执行数是查询结果数量加1次.
而sqlite3_column_**方法可以回去当前一条结果的字段数, 字段明, 和对应的不同类型的值.
RTDB同样提供了三中不同参数的方法,可以用来执行需要多次执行sqlite3_step的方法.
同时这三个方法都有对应的可以传出error信息的方法.
// 方法 0-1
- (RTNext *)execSql:(NSString *)sql, ... NS_REQUIRES_NIL_TERMINATION;
- (RTNext *)execSql:(NSString *)sql withArrArgs:(NSArray *)arrArgs;
- (RTNext *)execSql:(NSString *)sql withParams:(NSDictionary *)params;
建表,插入,删除,更新等操作也可通过方法 0-1 实现.
方法0-1的返回值是RTNext类型. RTNext 中封装了一些sqlite3_column_**可以方便的获取查询结果.
// 逐row回调数据
- (void)enumAllSteps:(RT_STEP_CALLBACK_BLOCK)stepCallback;
// 逐字段(column)回调数据
- (void)enumAllColumns:(RT_COLUMN_CALLBACK_BLOCK)columnCallback;
// 若sqlite3_step的结果为SQLITE_OK, 返回YES.
- (BOOL)step;
...
// 和一些获取每条column信息的方法, 不在赘述.
// 详情都在源码里.
RTDBDefault
RTDBDefault 继承自RTDB, 以上所述的方法, 仍然都可以使用. RTDBDefault是针对常使用的model类进行的封装. 通过runtime的方式获取了类的属性, 并以此缓存了类的一些信息, 和建表, 插入, 更新, 删除的sql语句.
在有 model 类的情况下, 使用RTDBDefault时, 建表, 插入, 更新, 删除不再用写没有智能提示(WTF!)的sql字符串语句.直接操作对象即可.
注意:
RTDBDefault 将 @property (assign) NSInteger _id; 作为table的primary key.
所以, 只有有 _id 属性的model 类才能够使用 RTDBDefault, 且 _id 的值由 sqlite3 在存入时, 自增赋值.
更新和删除操作便是依赖 _id 值定位索要删除的数据.
方法 1-0
- (BOOL)creatTable:(Class)cls withError:(NSError * __autoreleasing *)err;
- (BOOL)insertObj:(id)obj withError:(NSError * __autoreleasing *)err;
- (BOOL)updateObj:(id)obj withError:(NSError * __autoreleasing *)err;
- (BOOL)deleteObj:(id)obj withError:(NSError * __autoreleasing *)err;
方法 1-1 便是针对 model 类的查询方法. 还是要写 sql 语句的.
这两个方法, 是根据 sql 中的 table 的名字, 找出 model 类, 并将查询到的所有数据存入数组返回.
- fetchSql:withError: 是将每条 row 的数据先存入字典, 再存入数组返回.
- fetchObjSql:withError: 是将每条 row 的数据, 根据 sql table name 所对应的 model 类创建对象, 使用kvc赋值后存入数组返回.
方法 1-1
- (NSArray<NSDictionary *> *)fetchSql:(NSString *)sql withError:(NSError * __autoreleasing *)err;
- (NSArray *)fetchObjSql:(NSString *)sql withError:(NSError * __autoreleasing *)err;
RTSDB 与 RTSDBExtra
- RTSDB 与 RTSDBExtra 采用链式调用的形式实现. 我也写了一篇简单介绍响应式反应的文章, 仅供参考, 具体请看.
挺多人认为链式调用的编写, 让代码的可阅读性不高. 风格上也很难与其他代码统一. 其实是真的有这个问题.
而且在调用时, 只能提示也没有得到很好的支持.
但是链式调用带来的代码的简洁, 也是显而易见的.所以这不是哪个更好的问题, 而是选择问题.
- 对不起, 我选择好看的.(- -')
// 举个例子:
// 查询
RTSDB *db = [[RTSDB alloc] init];
// 切换到 异步线程
db.onQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))
// 传入执行的 sql
.execArgs(@"SELECT * FROM DB", nil)
// .onQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))
// 遍历查询结果
.onEnum(^(NSDictionary *dic, int step, BOOL *stop){
NSLog(@"%@", dic);
NSLog(@"%d", step);
NSLog(@"%@", [NSThread currentThread]);
})
// 查看错误回调
.onError(^(NSError *err) {
NSLog(@"%@", err);
});
- onMain: 其后操作都在主线程上执行
- onQueue: 其后操作都在传入的队列上执行
- onDefault: 如果设置了默认队列, 将在默认队列上执行, 否则在主队列上执行.
- 在响应式调用过程中, 可在任意位置调用以上三个方法,切换队列.
- 切换一次queue之后, 直到结束前, 若无其他切换queu的操作, 会在最近一次切换的queue上执行完所有操作.
- queue 为 dispatch_queue_t
gitHub的 readme 中有比较详细的用法代码.
- 本文中所有代码:
RTDatabase
之前在个人项目中实现并使用了RTSQLite, 发现了许多问题, 且代码也并没有规划就写了. 于是, 重写了RTSQLite 并起名 RTDatabase.
- gitHub主页:
FuihuiC
线程锁
- sqlite3默认 flags 包括了
SQLITE_OPEN_FULLMUTEX
, 所以 sqlite3 本身便是同步的. - 在其他需要使用锁的地方, RTDB使用了
dispatch_semaphore_t
.