近期又学习了一下数据库的东西,决定花时间封装一个数据库,看了一些源码,感受有必定的可行度,因此先把第一篇(本篇)文章发上来。啥都没有发上来干啥捏?有时候容易半途而废,先发上来,断本身后路!必定得写,否则就是说话不算话了。git
这个文章,咱们要从0开始封装一个面向OC对象的数据库,想了解怎么作的,能够一块儿陪伴一下,全部的流程细节我都会写在文章内,由于我也第一次搞这个东西,有兴趣的话我们能够一块儿讨论和提高。github
固然是指望作到简(无)单(脑)操做数据库,不须要背语句,也不须要解析模型,相似realm这种骚操做[realm addObject:obj];obj就给你存到数据库了。 sql
(原本说好的只发文章断本身后路,可是仍是先实现一部分功能,省得显得太空洞了) 本篇主要实现的功能有:数据库
首先建立一个工程,并向工程中拖入libsqlite3.0.tbd这个库数组
a、打开并建立数据库 sqlite3 向咱们提供了这个接口,用来执行打开数据库操做,第一个参数为数据库存的路径,第二个参数为sqlite3的操做链接。 若是数据库路径下没有数据库,则建立一个数据库并打开,若是有则直接打开数据库安全
SQLITE_API int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);
复制代码
咱们封装一个方法执行建立并打开数据库的代码,当传uid的时候会以uid命名数据库,若是没传将会默认数据库名称为CWDB,路径咱们写在CWDatabase.m下,由于是测试阶段,因此路径设置在桌面上。须要自行修改路径bash
+ (BOOL)openDB:(NSString *)uid {
// 数据库名称
NSString *dbName = @"CWDB.sqlite";
if (uid.length != 0) {
dbName = [NSString stringWithFormat:@"%@.sqlite", uid];
}
// 数据库路径
NSString *dbPath = [kCWDBCachePath stringByAppendingPathComponent:dbName];
// 打开数据库
int result = sqlite3_open(dbPath.UTF8String, &cw_database);
if (result != SQLITE_OK) {
NSLog(@"打开数据库失败! : %d",result);
return NO;
}
// 检测当前链接的数据库是否处于busy状态,处于则会回调CWDBBusyCallBack
sqlite3_busy_handler(cw_database, &CWDBBusyCallBack, (void *)(cw_database));
return YES;
}
复制代码
b、关闭数据库 传一个sqlite3的操做链接便可以将链接关闭多线程
SQLITE_API int sqlite3_close(sqlite3*);
复制代码
帖上咱们对应的代码框架
+ (void)closeDB {
if (cw_database) {
sqlite3_close(cw_database);
cw_database = nil;
}
}
复制代码
c、进行单元测试 选择以下图建立一个单元测试的类ide
a、调用sqlite3的API建立表格 sqlite为咱们提供下面这个方法在执行sql语句
//数据库执行语句
SQLITE_API int sqlite3_exec(
sqlite3*, /* sqlite3的操做链接 */
const char *sql, /* SQL语句 */
int (*callback)(void*,int,char**,char**), /* 回调函数 */
void *, /* 第一个参数的回调 */
char **errmsg /* 错误信息 */
);
复制代码
作数据库执行语句时,咱们的逻辑是:
+ (BOOL)execSQL:(NSString *)sql uid:(NSString *)uid {
// 打开数据库
if (![self openDB:uid]) {
return NO;
}
// 执行语句
char *errmsg = nil;
int result = sqlite3_exec(cw_database, sql.UTF8String, nil, nil, &errmsg);
// 关闭数据库
[self closeDB];
// 执行语句失败则抛出错误信息
if (result != SQLITE_OK) {
NSLog(@"exec sql error : %s",errmsg);
return NO;
}
return YES;
}
复制代码
一样的,咱们对这个方法进行单元测试,在这里咱们须要本身写sql的执行语句,测试传uid与不传uid两种状况,并断言会成功
- (void)testOpenDBAndExceSql {
NSString *sql = @"create table if not exists Student(id integer , name text not null, age integer, score real,primary key(id))";
BOOL result = [CWDatabase execSQL:sql uid:nil];
XCTAssertEqual(YES, result);
BOOL result1 = [CWDatabase execSQL:sql uid:@"Chavez"];
XCTAssertEqual(YES, result1);
}
复制代码
最终成功建立对应的两个数据库以及表格
create table if not exists Student(id integer , name text not null, age integer, score real,primary key(id))
create table if not exists 表名(字段1 字段1类型,字段2 字段2类型 ....., primary key(字段))
复制代码
其中字段和字段类型,能够对应成操做模型的成员变量以及成员变量的类型,因此,咱们经过runtime的方法,获取到模型的全部成员变量以及全部成员变量对应的类型。 咱们在CWModelTool这个类里面封装一个方法来获取模型全部成员变量的类型以及名称,封装成一个字典返回 字典的类型为 {成员变量名称(key) :成员变量类型(value)}
+ (NSDictionary *)classIvarNameAndTypeDic:(Class)cls {
unsigned int outCount = 0;
Ivar *varList = class_copyIvarList(cls, &outCount);
NSMutableDictionary *nameTypeDic = [NSMutableDictionary dictionary];
for (int i = 0; i < outCount; i++) {
Ivar ivar = varList[i];
// 1.获取成员变量名称
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([ivarName hasPrefix:@"_"]) {
ivarName = [ivarName substringFromIndex:1];
}
// 2.获取成员变量类型 @\" NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; type = [type stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"@\""]];
[nameTypeDic setValue:type forKey:ivarName];
}
return nameTypeDic;
}
复制代码
而后咱们进行单元测试,建立CWModelToolTests的单元测试并建立一个student的模型,模型的成员变量为
@interface Student : NSObject<CWModelProtocol>
{
float score;
}
@property (nonatomic,assign) int stuId; // 学号
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int height;
@end
复制代码
而后咱们在CWModelToolTests写一个单元测试的方法
- (void)testIvarNameTypeDict {
NSDictionary *dict = [CWModelTool classIvarNameAndTypeDic:[Student class]];
NSLog(@"Student------%@",dict);
XCTAssertNotNil(dict);
}
复制代码
而后咱们运行这个测试函数,在控制台获得以下打印:
2017-12-07 16:41:23.934525+0800 CWDB[34996:3867985] Student------{
age = i;
height = i;
name = NSString;
score = f;
stuId = i;
}
复制代码
与对应模型的成员变量一致,测试经过。 获取了对应的成员变量的字典后,咱们须要将这个字典转换成sql对应的语句,下面加粗的部分 create table if not exists 表名(字段1 字段1类型,字段2 字段2类型 ....., primary key(字段)) 在此以前,咱们还要进行另外一个转换,由于数据库里面对应的类型和OC的类型并不同,因此要变一变
暂时不考虑OC对象(数组,字典 等...)以及自定义对象的状况
OC 数据库
i : 整型 integer
q: long integer
Q: long long integer
B: bool integer
d: double real
f: float real
NSString: 字符串 text
NSData: 二进制 blob
复制代码
咱们封装一个函数来进行字典的转换,咱们要获得的字典类型**{成员变量名称(key) :成员变量对应数据库的类型(value)}**
+ (NSDictionary *)classIvarNameAndSqlTypeDic:(Class)cls {
// 获取模型的全部成员变量
NSMutableDictionary *classDict = [[self classIvarNameAndTypeDic:cls] mutableCopy];
[classDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
// 对应的数据库的类型从新赋值
classDict[key] = [self getSqlType:obj];
}];
return classDict;
}
// oc类型转换到数据库的类型
+ (NSString*)getSqlType:(NSString*)type{
if([type isEqualToString:@"i"]||[type isEqualToString:@"I"]||
[type isEqualToString:@"s"]||[type isEqualToString:@"S"]||
[type isEqualToString:@"q"]||[type isEqualToString:@"Q"]||
[type isEqualToString:@"b"]||[type isEqualToString:@"B"]||
[type isEqualToString:@"c"]||[type isEqualToString:@"C"]|
[type isEqualToString:@"l"]||[type isEqualToString:@"L"]) {
return @"integer";
}else if([type isEqualToString:@"f"]||[type isEqualToString:@"F"]||
[type isEqualToString:@"d"]||[type isEqualToString:@"D"]){
return @"real";
}else if ([type isEqualToString:@"NSData"]) {
return @"blob";
}else{
return @"text";
}
}
复制代码
这里咱们就不在贴测试的代码了,反正是成功的。 而后咱们将以上方法获取的字典转换成咱们须要的sql的字符串,也就是这种类型 **字段1 字段1类型,字段2 字段2类型 .....**声明主键后面在拼接
+ (NSString *)sqlColumnNamesAndTypesStr:(Class)cls {
NSDictionary *sqlDict = [[self classIvarNameAndSqlTypeDic:cls] mutableCopy];
NSMutableArray *nameTypeArr = [NSMutableArray array];
[sqlDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
[nameTypeArr addObject:[NSString stringWithFormat:@"%@ %@",key,obj]];
}];
return [nameTypeArr componentsJoinedByString:@","];
}
复制代码
同理。。这里咱们也不测试了。反正是成功的 create table if not exists 表名(字段1 字段1类型,字段2 字段2类型 ....., primary key(字段)) 这段语句,咱们还差一个表名和主键没有获取下面咱们给CWModelTool封装一个方法来获取表名,表名咱们是经过模型的类名拼接targetid组成的。
+ (NSString *)tableName:(Class)cls targetId:(NSString *)targetId {
return [NSString stringWithFormat:@"%@%@",NSStringFromClass(cls),targetId];
}
复制代码
在获取主键这里,有两种经常使用的方式,一种是设计一个自动增加的主键,另外一种是学习realm的方式,经过代理让用户为模型返回一个主键,这里咱们使用后者。在CWModelProtocol声明一个协议方法,且这个方法是必须实现的
@protocol CWModelProtocol <NSObject>
@required
/**
操做模型必须实现的方法,经过这个方法获取主键信息
@return 主键字符串
*/
+ (NSString *)primaryKey;
@end
复制代码
接下来,咱们封装建立数据库表格的最终方法 在CWSqliteModelTool内,封装一个方法
// uid用来确认哪一个数据库,targetId用来区分数据库表名
+ (BOOL)createSQLTable:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId {
// 建立数据库表的语句
// create table if not exists 表名(字段1 字段1类型(约束),字段2 字段2类型(约束)....., primary key(字段))
// 获取数据库表名
NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
if (![cls respondsToSelector:@selector(primaryKey)]) {
NSLog(@"若是想要操做这个模型,必需要实现+ (NSString *)primaryKey;这个方法,来告诉我主键信息");
return NO;
}
// 获取主键
NSString *primaryKey = [cls primaryKey];
if (!primaryKey) {
NSLog(@"你须要指定一个主键来建立数据库表");
return NO;
}
NSString *createTableSql = [NSString stringWithFormat:@"create table if not exists %@(%@, primary key(%@))",tableName,[CWModelTool sqlColumnNamesAndTypesStr:cls],primaryKey];
return [CWDatabase execSQL:createTableSql uid:uid];
}
复制代码
而后。。咱们来进行单元测试 新建一个CWSqliteModelToolTests单元测试类,用来测试CWSqliteModelTool的全部方法,而后新建一个Student模型,遵照CWModelProtocol协议,实现必需要的协议方法。
Student.h
#import <Foundation/Foundation.h>
#import "CWModelProtocol.h"
@interface Student : NSObject<CWModelProtocol>
{
float score;
}
@property (nonatomic,assign) int stuId; // 学号
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int height;
@end
Student.m
#import "Student.h"
@implementation Student
// 返回主键信息
+ (NSString *)primaryKey {
return @"stuId";
}
@end
复制代码
测试建立数据库表格方法
- (void)testCreateSQLTable {
BOOL result = [CWSqliteModelTool createSQLTable:[Student class] uid:@"CWDB" targetId:@"53class"];
XCTAssertTrue(result);
}
复制代码
运行以后获得以下结果
用户若是要建立一个表,只须要调用这个方法
+ (BOOL)createSQLTable:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId;
复制代码
将模型的类型,用户id(能够为空)以及目标id(能够为空)传过来,咱们就会建立对应的数据库并打开,解析模型,建立对应的表格,关闭数据库。
在此,咱们经过调用sqlite的API,经过runtime,将建立数据库表格的操做用很是简洁的API开放出来,目前仍是很成功的,在下一篇文章,咱们会实现数据库插入、查询、更新操做。。在更后面的文章,咱们会实现删除、存储模型内嵌套OC对象,以及数组内嵌套自定义模型,以及多线程安全等的处理。。
每一章的代码我会上传到github上。。并打tag做为一个节点。欢迎你们下载并查找漏洞,由于。我也是第一次封装。一块儿学习,一块儿进步。
github地址 tag为1.0.0,你能够在下图的位置找到他,并下载下来。
最后以为有用的同窗,但愿能给本文点个喜欢,给github点个star以资鼓励,谢谢你们。
PS: 由于我也是一边封装,一边写文章。效率可能比较低,问题也会有,欢迎你们向我抛issue,有更好的思路也欢迎你们留言!
目前第二篇文章已经出炉,地址:从0开始弄一个面向OC数据库(二)
最后再为你们推荐一个0耦合的侧滑框架。 一行代码集成超低耦合的侧滑功能