前言


  最近在GitHub上看了一份关于基于runtime封装的对象存储型数据库的开源代码,觉得非常值得分享记录一下,在IOS中对数据库的操作一般通过CoreData和SQLite,CoreData 虽然能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象,期间不需要编写SQL语句,但使用起来并不是那么方便,而SQLite则需要用户编写相应的数据库语句,看起来不是很美观,所以大家一般都会将其进行封装,让其使用起来更加方便,而LHDB就是建立在SQLite之上的封装。现在,我们来看其是如何实现的,不过在此之前,我先假设大家对OC的runtime机制和sqlite有一定的理解。附上源码下载地址:github链接

实现


  所谓的基于对象存储的数据库,顾名思义,就是一切对数据的操作都是对对象模型的操作,通过给对象模型属性赋值,然后将对象交由底层去解析,转化成相应的SQL语句,然后执行数据库操作,我们先看看整体的一个LHDB目录结构(这里仅写出头文件):

LHDB

  • LHDBPath.h                //记录数据库路径
  • LHModelStateMent.h           //提供一系列将对象模型转化成相应的SQL语句的接口
  • LHPredicate.h                    //条件语句处理类
  • LHSqlite.h                         //真正执行数据库操作的类
  • NSObject+LHDB.h              //对外提供一系列数据库操作接口

LHModel

  • LHObjectInfo.h                  //声明了两个类,LHClassInfo 记录类信息,LHObjectInfo 记录类对象属性的信息(包括属性Type,Getter和Setter)
  • NSObject+LHModel.h            //提供一系列对象模型信息和数据转换相关的接口

下面我将从我们正常使用数据库的流程去解析LHDB对数据库的管理

  1. 创建数据库表

    先声明了一个模型类Teacher,如下:

 @interface Teacher : NSObject

 @property (nonatomic,strong) NSString* name;

 @property (nonatomic,assign) NSInteger age;

 @property (nonatomic,strong) NSDate* updateDate;

 @property (nonatomic,strong) NSData* data;

 @end

然后我们调用声明在NSObject+LHDB.h中的类方法createTable,

 [Teacher createTable];
//建表
1 + (void)createTable
{
LHSqlite* sqlite = [LHSqlite shareInstance];
sqlite.sqlPath = [self dbPath];
[sqlite executeUpdateWithSqlstring:createTableString(self) parameter:nil];
}
//数据库路径
1 + (NSString*)dbPath
{
if ([LHDBPath instanceManagerWith:nil].dbPath.length == ) {
return DatabasePath;
}else
return [LHDBPath instanceManagerWith:nil].dbPath;
}

这里注意的一点就是在createTable方法中,对数据库路径的获取,它主要是在LHDBPath中指定了,如果没有指定,则使用默认,这意味着,我们可以在外部修改这个数据库路径名,便可以变更数据库了。

接下来调用了LHSqlite中 executeUpdateWithSqlstring:parameter: 方法真正对数据库进行相关对操作,在上面的调用中,该方法的第一个参数是一个SQL语句串,而第二个参数是一个属性值表,上面调用了一个全局方法createTableString(self),得到一个创建表的SQL语句,该方法声明在了LHModelStateMent中,其定义如下:

LHModelStateMent.m

 NSString* createTableString(Class modelClass)
{
NSMutableString* sqlString = [NSMutableString stringWithString:CREATE_TABLENAME_HEADER];
NSDictionary* stateMentDic = [modelClass getAllPropertyNameAndType]; //key为属性名,value为属性类型字符串,如:NSString,i,Q,d等等
[sqlString appendString:NSStringFromClass(modelClass)]; //类名做为表名
NSMutableString* valueStr = [NSMutableString string];
[stateMentDic enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* obj, BOOL* stop) {
obj = [NSString stringWithFormat:@"%@",obj];
[valueStr appendString:tableNameValueString(obj, key)];
}];
if (valueStr.length>) {
[valueStr deleteCharactersInRange:NSMakeRange(valueStr.length-, )];
}
[sqlString appendFormat:@"(%@)",valueStr];
return sqlString;
}
 #define CREATE_TABLENAME_HEADER @"CREATE TABLE IF NOT EXISTS "
#define INSERT_HEADER @"INSERT INTO "
#define UPDATE_HEADER @"UPDATE "
#define DELETE_HEADER @"DELETE FROM "
#define SELECT_HEADER @"SELECT * FROM "
 static NSString* tableNameValueString(NSString* type,NSString* name)
{
//将oc中的type字符串转换成sqlite能认识的类型type,组合成一个字符串,类似@"age INT,"返回,注意后面的","号 NSString* finalStr = @",";
NSString* typeStr = (NSString*)type;
if ([typeStr isEqualToString:@"i"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"INT",finalStr];
}else if ([typeStr isEqualToString:@"f"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"FLOAT",finalStr];
}else if ([typeStr isEqualToString:@"B"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"BOOL",finalStr];
}else if ([typeStr isEqualToString:@"d"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"DOUBLE",finalStr];
}else if ([typeStr isEqualToString:@"q"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"LONG",finalStr];
}else if ([typeStr isEqualToString:@"NSData"]||[typeStr isEqualToString:@"UIImage"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"BLOB",finalStr];
}else if ([typeStr isEqualToString:@"NSNumber"]){
return [NSString stringWithFormat:@"%@ %@%@",name,@"INT",finalStr];
} else //可见其他类型,将被当做TEXT类型处理,包括NSDictionary,NSArray
return [NSString stringWithFormat:@"%@ %@%@",name,@"TEXT",finalStr];
}

上面标红的方法getAllPropertyNameAndType是一个类方法,被声明在NSObject+LHModel中,返回的是记录类的所有属性名和其相应类型的字典。

NSObject+LHModel.m

 + (NSDictionary*)getAllPropertyNameAndType
{
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
unsigned int count = ;
objc_property_t* property_t = class_copyPropertyList(self, &count);
for (int i=; i<count; i++) {
objc_property_t propert = property_t[i];
NSString* propertyName = [NSString stringWithUTF8String:property_getName(propert)];
NSString* propertyType = [NSString stringWithUTF8String:property_getAttributes(propert)];
[dic setValue:objectType(propertyType) forKey:propertyName];
}
free(property_t);
return dic;
}
 static id objectType(NSString* typeString)
{
//当typeString表示是一个oc对象类型的时候,它看起来类似这样:@"T@\"NSString\",&,N,V_name"
//否则,它看起来类似这样:@"Ti,N,V_age"
if ([typeString containsString:@"@"]) { //type为oc对象时,typeString值类似 @"@\"NSString\"",这时候,分割之后返回的strArray[0]是 @"T@",strArray[1]就是@"NSString"了
NSArray* strArray = [typeString componentsSeparatedByString:@"\""];
if (strArray.count >= 1) {
return strArray[1];
}else
return nil;
}else
return [typeString substringWithRange:NSMakeRange(1, 1)];
}

下面终于到了最后一步,就是executeUpdateWithSqlstring:parameter:的调用,它基本的一个过程就是,先打开数据库,这跟我们之前在LHDBPath中指定的路径关联,指定哪个就打开哪个数据库,然后会从缓存中根据sqlString,读取相应的sqlite3_stmt结构数据,如果存在,就reset它,然后重新绑定参数,如果不存在,那就进行转换,然后再保存到缓存中,其具体定义如下:

LHSqlite.m

 - (void)executeUpdateWithSqlstring:(NSString *)sqlString parameter:(NSDictionary*)parameter
{
Lock;
if ([self openDB]) {
sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString];
if (stmt) {
for (int i=; i<parameter.allKeys.count; i++) {
[self bindObject:parameter[parameter.allKeys[i]] toColumn:i+ inStatement:stmt];
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
LHSqliteLog(@"error = %@",errorForDataBase(sqlString, _db));
}
}
}else {
LHSqliteLog(@"打开数据库失败");
}
sqlite3_close(_db);
UnLock;
}
其中stmtWithCacheKey:返回sqlite3_stmt结构类型指针,
 - (sqlite3_stmt*)stmtWithCacheKey:(NSString*)sqlString
{
sqlite3_stmt* stmt = (sqlite3_stmt*)CFDictionaryGetValue(_stmtCache, (__bridge const void *)([[self.sqlPath lastPathComponent] stringByAppendingString:sqlString]));
if (stmt == 0x00) {
if (sqlite3_prepare_v2(_db, sqlString.UTF8String, -, &stmt, nil) == SQLITE_OK) {
//缓存stmt
CFDictionarySetValue(_stmtCache, (__bridge const void *)([[self.sqlPath lastPathComponent] stringByAppendingString:sqlString]), stmt);
return stmt;
}else {
LHSqliteLog(@"error = %@",errorForDataBase(sqlString, _db));
return nil;
}
}else
sqlite3_reset(stmt);
return stmt;
}

这里使用了缓存了,sqlite3_prepare_v2函数,将一个SQL命令字符串转换成一条prepared语句,存储在sqlite3_stmt类型结构体中,sqlite3_prepare_v2函数代价昂贵,所以通常尽可能的重用prepared语句。

之后,执行[self bindObject]语句,根据传入的值类型,调用相应的sqlite3_bind_xxx方法,进行参数绑定,之后调用sqlite3_step执行,下面是bindObject的定义:

 - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {

     if ((!obj) || ((NSNull *)obj == [NSNull null])) {
sqlite3_bind_null(pStmt, idx);
} else if ([obj isKindOfClass:[NSData class]]) {
const void *bytes = [obj bytes];
if (!bytes) { bytes = "";
}
sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
}
else if ([obj isKindOfClass:[NSDate class]]) {
if (self.dateFormatter)
sqlite3_bind_text(pStmt, idx, [[self.dateFormatter stringFromDate:obj] UTF8String], -, SQLITE_STATIC);
else
sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String],-,SQLITE_STATIC);
}
else if ([obj isKindOfClass:[NSNumber class]]) { if (strcmp([obj objCType], @encode(char)) == ) {
sqlite3_bind_int(pStmt, idx, [obj charValue]);
}
else if (strcmp([obj objCType], @encode(unsigned char)) == ) {
sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
}
else if (strcmp([obj objCType], @encode(short)) == ) {
sqlite3_bind_int(pStmt, idx, [obj shortValue]);
}
else if (strcmp([obj objCType], @encode(unsigned short)) == ) {
sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
}
else if (strcmp([obj objCType], @encode(int)) == ) {
sqlite3_bind_int(pStmt, idx, [obj intValue]);
}
else if (strcmp([obj objCType], @encode(unsigned int)) == ) {
sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
}
else if (strcmp([obj objCType], @encode(long)) == ) {
sqlite3_bind_int64(pStmt, idx, [obj longValue]);
}
else if (strcmp([obj objCType], @encode(unsigned long)) == ) {
sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
}
else if (strcmp([obj objCType], @encode(long long)) == ) {
sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
}
else if (strcmp([obj objCType], @encode(unsigned long long)) == ) {
sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
}
else if (strcmp([obj objCType], @encode(float)) == ) {
sqlite3_bind_double(pStmt, idx, [obj floatValue]);
}
else if (strcmp([obj objCType], @encode(double)) == ) {
NSLog(@"%f",[obj doubleValue]);
sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
}
else if (strcmp([obj objCType], @encode(BOOL)) == ) {
sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? : ));
}
else {
sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -, SQLITE_STATIC);
}
}
else if ([obj isKindOfClass:[NSArray class]]||[obj isKindOfClass:[NSDictionary class]]) {
@try {
NSData* data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:nil];
NSString* jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
sqlite3_bind_text(pStmt, idx, [[jsonStr description] UTF8String], -, SQLITE_STATIC);
}
@catch (NSException *exception) { }
@finally { } }else if ([obj isKindOfClass:NSClassFromString(@"UIImage")]) {
NSData* data = UIImagePNGRepresentation(obj);
const void *bytes = [data bytes];
if (!bytes) {
bytes = "";
}
sqlite3_bind_blob(pStmt, idx, bytes, (int)[data length], SQLITE_STATIC);
}
else {
sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -, SQLITE_STATIC);
}
}

至此,我们的数据库创建和表的创建已经完成了,如果上面的过程你都能清楚了,那么后面数据库的内容,你会觉得还是比较轻松的,因为都差不多^-^。

  2.  SQL插入和查询

  对SQL插入数据记录,可以看到一个数据记录是怎么从Model一步步变成一条SQL语句,而数据的查询,则可以看到SQL查询的数据怎么映射到一个Model中,其它的数据库操作,我觉得类同,不多做阐述。

  • SQL插入数据

  首先,我们假设将插入一条Teacher信息记录,代码如下:

 //直接将model插入数据库
Teacher* teacher = [[Teacher alloc] init];
teacher.name = @"tom";
teacher.age = ;
teacher.data = [@"my name is tom" dataUsingEncoding:NSUTF8StringEncoding];
teacher.updateDate = [NSDate date];
[teacher save];

NSObject+LHDB.m

 - (void)save
{
LHSqlite* sqlite = [LHSqlite shareInstance];
sqlite.sqlPath = [self dbPath];
[sqlite executeUpdateWithSqlstring:insertString(self) parameter:[self lh_ModelToDictionary]];
}

正如你看到的,这个插入数据的方法跟上面数据库创表时很像,只不过它是类方法,而save是一个实例方法,仅此而已,唯一不同的只是在调用

executeUpdateWithSqlstring:parameter: 传入的两个参数,第一个是插入SQL语句,第二个是SQL语句参数表,看看它们的具体定义:

LHModelStateMent.m

 NSString* insertString(id model)
{
NSMutableString* sqlString = [NSMutableString stringWithString:INSERT_HEADER];
[sqlString appendString:NSStringFromClass([model class])];
NSDictionary* valueDic = [model lh_ModelToDictionary];
NSMutableString* keyStr = [NSMutableString string];
NSMutableString* valueStr = [NSMutableString string];
for (int i=; i<valueDic.allKeys.count; i++) {
NSDictionary* dic = insertValueString(valueDic.allKeys[i]);
[keyStr appendFormat:@"%@,",dic.allKeys[]];
[valueStr appendFormat:@"%@,",dic[dic.allKeys[]]];
}
[sqlString appendFormat:@"(%@) VALUES (%@)",[keyStr substringToIndex:keyStr.length-],[valueStr substringToIndex:valueStr.length-]]; //这里sqlString的值类似于"insert into tablename (name,age) values (?,?)",后面的值中的?将会在后面调用sqlite3_bind_xx绑定参数的时候,被相应参数值依次替代
return sqlString;
}
 static NSDictionary* insertValueString(id value,NSString* name,NSString* type)
{
return @{name:@"?"};

下面就到了我们的重点了,-(NSDictionary*)lh_ModelToDictionary ,它的功能就是将对象模型Model转换成表结构的方法,具体来看它的实现:

NSObject+LHModel.m

 - (NSDictionary*)lh_ModelToDictionary
{
if ([self isKindOfClass:[NSArray class]]) {
return nil;
}else if ([self isKindOfClass:[NSDictionary class]]){
return (NSDictionary*)self;
}else if ([self isKindOfClass:[NSString class]]||[self isKindOfClass:[NSData class]]) {
return [NSJSONSerialization JSONObjectWithData:dataFromObject(self) options:NSJSONReadingMutableContainers error:nil];
}else {
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
ModelSetContext context = {};
context.classInfo = (__bridge void *)(dic);
context.model = (__bridge void *)(self); //注意: 这里保存了OC对象本身,后续的属性遍历中,会使用这个model去调用它的属性getter等方法
LHClassInfo* classInfo;
//判断缓存中是否有这个类的信息
if ([LHClassInfo isCacheWithClass:object_getClass(self)]) {
classInfo = [LHClassInfo classInfoWithClass:object_getClass(self)];
}else
//这里记录类信息,并缓存类信息
classInfo = [[LHClassInfo alloc] initWithClass:object_getClass(self)]; //遍历objectInfoDic的key(属性名)和value(结构类型,LHObjectInfo*),得到一个新字典(key:属性名,value:字段值),保存在context.classInfo中,也就是上面的dic;
CFDictionaryApplyFunction((__bridge CFMutableDictionaryRef)classInfo.objectInfoDic, ModelGetValueToDic, &context);
return dic;
}
return nil;
}

注意的地方就是,当OC对象类型是非集合类的时候,首先,它定义了一个临时结构体类型 ModelSetContext context = {0},并初始化了classInfo(可变表结构),和model(模型本身,self)字段值,然后调用LHClassInfo的类方法isCacheWithClass判断对象所属类的信息是否已经登记在全局缓存中,如果有,则返回类信息指针,结构类型为LHClassInfo,否则,调用-initWithClass创建类信息对象,同时保存到缓存中,最后调用CFDictionaryApplyFunction,这个方法苹果文档的描述,就是“Calls a function once for each key-value pair in a dictionary.”,就是遍历字典的每个键值对,它的第一个参数是要操作的字典dictionary,第二个参数是回调方法,第三个参数则是回调方法的第三个参数,将刚刚创建的context地址传入,这个方法,遍历类信息中的所有属性信息,然后利用objc_msgSend调用属性信息中保存的getter方法,得到每个属性的值,并将键值保存到context的classInfo中,从而得到一个记录了对象属性和其对应值的表,相关方法定义如下:

NSObject+LHModel.m

//获取属性对应的字段的值,保存在context->classInfo中
static void ModelGetValueToDic(const void* key,const void* value,void* context)
{
ModelSetContext* modelContext = context;
NSMutableDictionary* dic = (__bridge NSMutableDictionary *)(modelContext->classInfo);
id object = (__bridge id)(modelContext->model);
NSString* dicKey = (__bridge NSString *)(key);
LHObjectInfo* objectInfo = (__bridge LHObjectInfo*)(value);
if (objectInfo) {
if (objectInfo.cls) {
[dic setValue:((id(*)(id,SEL))(void*) objc_msgSend)(object,objectInfo.get) forKey:dicKey];;
}else if (objectInfo.type.length>) {
NSNumber* number = getBaseTypePropertyValue(object, objectInfo.baseTypeEcoding, objectInfo.get);
[dic setValue:number forKey:dicKey];
}
}
}
 static NSNumber* getBaseTypePropertyValue(__unsafe_unretained NSObject* object, NSUInteger type,SEL get)
{
switch (type) {
case LHBaseTypeEcodingINT: return @(((int (*)(id, SEL))(void *) objc_msgSend)(object, get)); case LHBaseTypeEcodingLONG: return @(((long (*)(id, SEL))(void *) objc_msgSend)(object,get)); case LHBaseTypeEcodingULONG: return @(((NSUInteger(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingFLOAT: return @(((float(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingDOUBLE: return @(((double(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingBOOL: return @(((BOOL(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingCHAR: return @(((char(*)(id,SEL))(void*) objc_msgSend)(object,get)); default:
return nil;
break;
}
}

LHObjectInfo.h

 //对象类信息
@interface LHClassInfo : NSObject @property (nonatomic)Class cls; @property (nonatomic)Class superClass; @property (nonatomic)Class metaClass; @property (nonatomic,assign) BOOL isMetaClass; @property (nonatomic,strong) NSMutableDictionary* objectInfoDic; - (instancetype)initWithClass:(Class)cls; + (BOOL)isCacheWithClass:(Class)cls; + (LHClassInfo*)classInfoWithClass:(Class)cls; - (LHObjectInfo*)objectInfoWithName:(NSString*)name; @end

其中objectInfoDic记录了对象所有属性的信息,它的原型是LHObjectInfo,

 typedef NS_ENUM(NSUInteger,LHBaseTypeEcoding) {
LHBaseTypeEcodingUnknow,
LHBaseTypeEcodingINT,
LHBaseTypeEcodingLONG,
LHBaseTypeEcodingULONG,
LHBaseTypeEcodingCHAR,
LHBaseTypeEcodingFLOAT,
LHBaseTypeEcodingBOOL,
LHBaseTypeEcodingDOUBLE
}; typedef NS_ENUM(NSUInteger,LHNSTypeEcoding) {
LHNSTypeUNknow,
LHNSTypeNSString,
LHNSTypeNSNumber,
LHNSTypeNSDate,
LHNSTypeNSData,
LHNSTypeNSURL,
LHNSTypeNSArray,
LHNSTypeNSDictionary,
LHNSTypeUIImage
}; //描述对象属性的结构
@interface LHObjectInfo : NSObject @property (nonatomic) Class cls; //当属性是OC对象时,cls记录属性对象所属类,否则为基础类型时,值为nil @property (nonatomic) objc_property_t property_t; //属性 @property (nonatomic,copy) NSString* name; //属性名 @property (nonatomic,assign) LHBaseTypeEcoding baseTypeEcoding;  //自定义基础数据类型编码 @property (nonatomic,assign) LHNSTypeEcoding nsTypeEcoding; //自定义OC对象类型编码 @property (nonatomic) SEL set; //属性的setter方法 @property (nonatomic) SEL get; //属性的getter方法 @property (nonatomic,copy) NSString* type; //对象类型,如:NSString,i,Q,d等等 - (instancetype)initWithProperty:(objc_property_t)property; @end

LHObjectInfo.m

LHClassInfo类实现

 - (instancetype)initWithClass:(Class)cls
{
self = [super init];
if (self) {
_cls = cls;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objectInfoCacheDic = [NSMutableDictionary dictionary];
});
_objectInfoDic = [NSMutableDictionary dictionary]; //遍历属性,为每个属性生成 LHObjectInfo 对象 并保存在类字典objectInfoDic中,注意是类
unsigned int count;
objc_property_t* t = class_copyPropertyList(cls, &count);
for (int i=; i<count; i++) { LHObjectInfo* info = [[LHObjectInfo alloc] initWithProperty:t[i]];
[_objectInfoDic setValue:info forKey:[NSString stringWithUTF8String:property_getName(t[i])]];
}
free(t); //记录类名对应的类信息,并保存在静态全局缓存字典objectInfoCacheDic中
[objectInfoCacheDic setValue:self forKey:NSStringFromClass(cls)];
}
return self;
} + (BOOL)isCacheWithClass:(Class)cls
{
if ([objectInfoCacheDic objectForKey:NSStringFromClass(cls)]) {
return YES;
}
return NO;
} + (LHClassInfo*)classInfoWithClass:(Class)cls
{
return objectInfoCacheDic[NSStringFromClass(cls)];
}

LHObjectInfo类实现

 - (instancetype)initWithProperty:(objc_property_t)property
{
if (property == nil) return nil;
self = [super init];
if (self) {
_property_t = property;
_name = [NSString stringWithUTF8String:property_getName(property)]; //记录属性名 unsigned int count;
objc_property_attribute_t* t = property_copyAttributeList(property, &count);
//for (unsigned int i=0; i<count; i++) {
if(count > ){
//源代码是一个循环,其实这里只是获取第一个属性值,也就是T,即是属性的类型,所以我这里改成了一个判断语句,将t[i] 改成 t[0]
objc_property_attribute_t p = t[];
size_t len = strlen(p.value); //假设属性是NSString,则p.name = "T",p.value = "@\"NString\"";
if (len > ) {
char name[len - ];
name[len - ] = '\0';
memcpy(name, p.value + , len - );
_cls = objc_getClass(name); //记录类
_type = [NSString stringWithUTF8String:name]; //记录对象类型
_nsTypeEcoding = nsTypeEcoding(_type); //记录oc对象编码,目前只支持NSString,NSNumber,NSDate,NSData,NSURL,NSArray,NSDictionary,UIImage
//break;
}else {
//基础数据类型
_type = [NSString stringWithUTF8String:p.value];
if (_type.length>) {
_type = [_type substringToIndex:];
}
if (_type.length>) {
_baseTypeEcoding = baseTypeEcoding([_type characterAtIndex:]);
}
//break;
}
}
free(t); if (_name.length>) {
_set = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[[_name substringToIndex:] uppercaseString],[_name substringFromIndex:]]);
_get = NSSelectorFromString(_name);
}
}
return self;
}

LHClassInfo和LHObjectInfo分别保存了类的相关信息和属性信息,在LHObjectInfo的initWithProperty方法中,大家也看到最后属性的setter是由"set+属性名(首字母大写)"得到的,getter也是和属性名一致的,所以大家在定义模型的时候不要去自己自定义自己的属性setter和getter方法,这会导致可能不可预知的数据错误,同时,不应该去声明变量,应该总是使用属性,因为我们在转换的时候都是使用了模型的属性列表来做转换。

好了,至此,我们又一次完成了对数据记录插入的分析^-^。

  • SQL查询数据

  数据的查询,其实就是根据条件,在相应的表中查询出满足条件的数据集合。  

  同样,从外部调用开始:

 //查询数据
LHPredicate* predicate = [LHPredicate predicateWithFormat:@"name = '%@'",@"tom"]; NSArray* result = [Teacher selectWithPredicate:predicate];
NSLog(@"result1 = %@",result);

NSObject+LHDB.m

 //查询记录不需要对象方法
+ (NSArray*)selectWithPredicate:(LHPredicate*)predicate
{
LHSqlite* sqlite = [LHSqlite shareInstance];
sqlite.sqlPath = [self dbPath];
NSArray* array = [sqlite executeQueryWithSqlstring:selectString(self, predicate)];
NSMutableArray* resultArray = [NSMutableArray array];
for (NSDictionary* dic in array) {
[resultArray addObject:[self lh_ModelWithDictionary:dic]];
}
return resultArray;
}

LHModelStateMent.m

 NSString* selectString(Class modelClass,LHPredicate* predicate)
{
NSMutableString* selectStr = [NSMutableString stringWithString:SELECT_HEADER];
[selectStr appendString:NSStringFromClass(modelClass)];
if (predicate.predicateFormat) {
[selectStr appendFormat:@" WHERE %@",predicate.predicateFormat];
}
if (predicate.sortString) {
[selectStr appendFormat:@" ORDER BY %@",predicate.sortString];
}
return selectStr;
}

LHSqlite.m

 - (NSArray*)executeQueryWithSqlstring:(NSString*)sqlString
{
Lock;
NSArray* resultArray = 0x00;
if ([self openDB]) {
sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString];
if (stmt) {
NSMutableArray* dataSource = [NSMutableArray array];
int count = sqlite3_column_count(stmt);
while (sqlite3_step(stmt) == SQLITE_ROW) {
NSMutableDictionary* dataDic = [NSMutableDictionary dictionary];
for (int i=; i<count; i++) {
int type = sqlite3_column_type(stmt, i);
NSString* propertyName = [NSString stringWithUTF8String:sqlite3_column_name(stmt, i)];
NSObject* value = dataWithDataType(type, stmt, i);
[dataDic setValue:value forKey:propertyName];
}
[dataSource addObject:dataDic];
}
resultArray = dataSource;
}
}
sqlite3_close(_db);
UnLock;
return resultArray;
}

这里,前面类似创建表、插入、更新、删除,都是使用了LHSqlite的executeUpdateWithSqlstring:parameter:方法,而查询调用的是executeQueryWithSqlstring:方法,这里将每条数据查询出来之后,通过dataWithDataType获得每个属性对应的值,得到每个记录的数据存放在字典中,最后将每个表放到数组对象中,返回。dataWithDataType定义如下:

LHSqlite.m

 static NSObject* dataWithDataType(int type,sqlite3_stmt * statement,int index)
{
if (type == SQLITE_INTEGER) {
int value = sqlite3_column_int(statement, index);
return [NSNumber numberWithInt:value];
}else if (type == SQLITE_FLOAT) {
float value = sqlite3_column_double(statement, index);
return [NSNumber numberWithFloat:value];
}else if (type == SQLITE_BLOB) {
const void *value = sqlite3_column_blob(statement, index);
int bytes = sqlite3_column_bytes(statement, index);
return [NSData dataWithBytes:value length:bytes];
}else if (type == SQLITE_NULL) {
return nil;
}else if (type == SQLITE_TEXT) {
return [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, index)];
}else {
return nil;
}
}

到此,我们看回selectWithPredicate:方法,方法中取出从executeQueryWithSqlstring:返回的结果后,遍历每个记录,并调用了lh_ModelWithDictionary这个类方法,得到每个对象Model,

 //从数据字典中还原model
+ (id)lh_ModelWithDictionary:(NSDictionary*)dic
{
if (!dic ||![dic isKindOfClass:[NSDictionary class]]) return nil;
NSObject* object = [[self alloc] init]; //创建一个类的对象
ModelSetContext context = {};
LHClassInfo* info;
//判断缓存中是否有这个类的信息
if ([LHClassInfo isCacheWithClass:self]) {
info = [LHClassInfo classInfoWithClass:self];
}else
info = [[LHClassInfo alloc] initWithClass:self];
context.classInfo = (__bridge void *)(info);
context.model = (__bridge void *)(object); //将类对象赋给model,下面的遍历方法中会使用该对象去调用属性的setter方法 CFDictionaryApplyFunction((__bridge CFDictionaryRef)dic, ModelSetValueToProperty, &context);
return object;
}

这里先创建了一个OC对象,然后获取类信息,因为我们在外部调用的时候,[Teacher selectWithPredicate:predicate]; 所以这里创建的类信息自然是Teacher类的信息结构,同样这里也声明了一个ModelSetContext结构对象,但是这里classInfo保存的是一个描述Model类信息的LHClassInfo指针,它被传入ModelSetValueToProperty方法中,由于LHClassInfo保存了类所有属性的信息,通过属性名和数据字典的key值对比,找到相应的属性,最后调用对象属性的setter方法,相关方法定义如下:

NSObject+LHModel.m

 static void ModelSetValueToProperty(const void *key, const void *value, void *context)
{
ModelSetContext* modelContext = context;
NSString* dicKey = (__bridge NSString *)(key);
id dicValue = (__bridge id)(value);
LHObjectInfo* objectInfo = [((__bridge LHClassInfo*)modelContext->classInfo) objectInfoWithName:dicKey]; //根据属性名获取属性信息结构
NSObject* object = (__bridge NSObject*)modelContext->model;
if (objectInfo) {
if (objectInfo.cls) {
setNSTypePropertyValue(object, dicValue, objectInfo.nsTypeEcoding, objectInfo.set); }else if (objectInfo.type.length>) {
NSNumber* number = numberWithValue(dicValue);
setBaseTypePropertyValue(object, number, objectInfo.baseTypeEcoding,objectInfo.set);
}
}
}
 static NSNumber* numberWithValue(__unsafe_unretained id value)
{
if (!value) {
return nil;
}
if ([value isKindOfClass:[NSNumber class]]) return value;
if ([value isKindOfClass:[NSString class]]) {
if ([value containsString:@"."]) { const char *cstring = ((NSString *)value).UTF8String;
if (!cstring) return nil;
double num = atof(cstring);
if (isnan(num) || isinf(num)) return nil; //判断浮点数是否是非数字或者无限大
return @(num);
}else {
const char *cstring = ((NSString*)value).UTF8String;
if (!cstring) return nil;
NSNumber* number = @(atoll(cstring));
if (!atoll(cstring)) {
number = [NSNumber numberWithChar:*(cstring+)];
}
return number;
}
}
return nil; }
 static void setBaseTypePropertyValue(__unsafe_unretained NSObject* object,__unsafe_unretained NSNumber* value, NSUInteger type,SEL set)
{
switch (type) {
case LHBaseTypeEcodingINT:
((void (*)(id, SEL, int))(void *) objc_msgSend)(object, set, value.intValue);
break; case LHBaseTypeEcodingLONG:
((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.integerValue);
break;
case LHBaseTypeEcodingULONG:
((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.unsignedIntegerValue);
break; case LHBaseTypeEcodingFLOAT:
((void(*)(id,SEL,float))(void*) objc_msgSend)(object,set,value.floatValue);
break;
case LHBaseTypeEcodingDOUBLE:
((void(*)(id,SEL,double))(void*) objc_msgSend)(object,set,value.doubleValue);
break;
case LHBaseTypeEcodingBOOL:
((void(*)(id,SEL,BOOL))(void*) objc_msgSend)(object,set,value.boolValue);
break; case LHBaseTypeEcodingCHAR:
((void(*)(id,SEL,char))(void*) objc_msgSend)(object,set,value.charValue);
break;
default:
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,nil);
break;
}
} static void setNSTypePropertyValue(__unsafe_unretained id object,__unsafe_unretained id value,LHNSTypeEcoding typeEcoding,SEL set)
{
switch (typeEcoding) {
case LHNSTypeUNknow:
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
break; case LHNSTypeNSString:
//将其它类型转成nsstring类型
if ([value isKindOfClass:[NSString class]]) {
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
}else if ([value isKindOfClass:[NSNumber class]]) {
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[value stringValue]);
}else if ([value isKindOfClass:[NSData class]]) {
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);
}else if ([value isKindOfClass:[NSDate class]]) {
((void(*)(id,SEL,NSString*))(void*) objc_msgSend)(object,set,stringFormDate(value));
}else
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
break; case LHNSTypeNSNumber:
((void(*)(id,SEL,NSNumber*))(void*) objc_msgSend)(object,set,numberWithValue(value));
break; case LHNSTypeNSDate:
if ([value isKindOfClass:[NSDate class]]) {
((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,value);
}else if ([value isKindOfClass:[NSString class]]) {
((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(value));
}else if ([value isKindOfClass:[NSData class]]) {
NSString* dateStr = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(dateStr));
}else
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
break; case LHNSTypeNSData:
((void(*)(id,SEL,NSData*))(void*) objc_msgSend)(object,set,dataFromObject(value));
break; case LHNSTypeNSURL:
((void(*)(id,SEL,NSURL*))(void*) objc_msgSend)(object,set,urlFromObject(value));
break; case LHNSTypeNSArray:
((void(*)(id,SEL,NSArray*))(void*) objc_msgSend)(object,set,arrayFromObject(value));
break; case LHNSTypeNSDictionary:
((void(*)(id,SEL,NSDictionary*))(void*) objc_msgSend)(object,set,dicFromObject(value));
break; case LHNSTypeUIImage:
((void(*)(id,SEL,UIImage*))(void*) objc_msgSend)(object,set,imageFromObject(value));
break; default:
break;
}
}

至此,我们已经完成了对数据库数据插入和查询的分析,至于删除和更新操作,基本都是一样的流程,这里不多阐述,大家可以去看源代码实现:https://github.com/ginvar/LHDB

总结


  LHDB虽然是对象存储型数据库,思路还是非常不错的,至少在使用上比较方便,但是,它还有一些不足我不得不去指出,在应对比较复杂的数据库操作的时候,或者数据库更新上面,我发现其实它并不支持,就比如说当我们app发布后,假设我们业务需求需要新增数据库表字段,它就不行,它需要你将数据库表都删除之后,然后再重新创建,这样意味着用户会丢弃了他的本地数据,这显然不合理,而我们目前一个强大的app,必然要应对这样复杂的使用环境,此外,在阅读源码的过程中,我也自己改动了一些方法实现,可能我觉得有些实现还不够精简,不过它作为学习拓展,我觉得还是一个比较不错的开源代码的,就这样了^-^。



runtime实现对象存储型数据库——LHDB的更多相关文章

  1. RRDTool 存储原理简介——基于时间序列的环型数据库

    转自:http://www.jianshu.com/p/b925b1584ab2 RRDTool是一套监测工具,可用于存储和展示被监测对象随时间的变化情况.比如,我们在 Windows 电脑上常见的内 ...

  2. FMDB将对象放进数据库[二](使用runtime)

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  3. java开发之阿里云对象存储OSS和云数据库Memcache的使用

    web开发中标配:aliyun ECS(阿里云服务器),aliyun RDS(阿里云数据库),aliyun OSS(阿里云对象存储),aliyun Memcache(阿里云缓存数据库). 今天就介绍下 ...

  4. 在ThinkPHP框架(5.0.24)下引入Ueditor并实现向七牛云对象存储上传图片同时将图片信息保存到MySQL数据库,同时实现lazyload懒加载

    这是我花了很多天的时间才得以真正实现的一组需求. 文章后面有完整Demo的GitHub链接. 一. 需求描述 1. 应用是基于ThinkPHP5开发的: 2. 服务器环境是LNMP,PHP版本是7.2 ...

  5. 【巨杉数据库Sequoiadb】巨杉⼯具系列之一 | ⼤对象存储⼯具sdblobtool

    近期,巨杉数据库正式推出了完整的SequoiaDB 工具包,作为辅助工具,更好地帮助大家使用和运维管理分布式数据库.为此,巨杉技术社区还将持续推出工具系列文章,帮助大家了解巨杉数据库丰富的工具矩阵. ...

  6. DevOps之存储和数据库

    唠叨话 关于德语噢屁事的知识点,仅提供专业性的精华汇总,具体知识点细节,参考教程网址,如需帮助,请留言. <数据(Data)> 了解有关数据部分.涉及存储及数据库的概念:知识与技能的层次( ...

  7. HDFS对象存储--Ozone架构设计

    前言 如今做云存储的公司非常多,举2个比較典型的AWS的S3和阿里云.他们都提供了一个叫做对象存储的服务,就是目标数据是从Object中进行读写的,然后能够通过key来获取相应的Object,就是所谓 ...

  8. 关于数据库管理系统DBMS--关系型数据库(MySQL/MariaDB)

    数据库的结构(3种):层次,网状,关系型(用的最多): DBMS的三层模型: 视图层:面向最终用户: 逻辑层:面向程序员或DBA: 物理层:面向系统管理员: 关系型数据库管理系统——RDBMS: 主要 ...

  9. 非关系统型数据库-mangodb

    第三十六课 非关系统型数据库-mangodb 目录 二十四 mongodb介绍 二十五 mongodb安装 二十六 连接mongodb 二十七 mongodb用户管理 二十八 mongodb创建集合. ...

随机推荐

  1. Coursera 机器学习笔记(一)

    主要是第一二周内容 机器学习概要 机器学习是什么? 生活在信息时代的我们,其实时时刻刻都离不开机器学习算法.比如日常使用的搜索引擎就涉及到很多学习算法. Arthur Samuel 给出第一个定义.他 ...

  2. NodeMCU透传数据到TcpServer和Yeelink平台

    准备工作 1. NodeMCU  LUA ESP8266 CP2102  WIFI Internet Development Board,仔细看背面可以看出自带cp2102模块,可以通过普通的手机充电 ...

  3. C++ #if #endif #define #ifdef #ifndef #if defined #if !defined详解 (转)

    (源)http://blog.csdn.net/sky1203850702/article/details/42024673 首先,让我们先从头文件开始,在很多头文件里,我们会看到这样的语句 #ifn ...

  4. std::forward_list

    forward_list相比list来说空间利用率更好,与list一样不支持随机访问,若要访问除头尾节点的其他节点则时间复杂度为线性. 在forward_list成员函数里只能访问头节点以及向头节点插 ...

  5. Spring+SpringMVC+MyBatis深入学习及搭建(十)——MyBatis逆向工程

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6973266.html 前面讲到:Spring+SpringMVC+MyBatis深入学习及搭建(九)--My ...

  6. docker- 构建 oracle2c-r2(12.2.0.1) 的镜像

    需求 由于公司要数据库需要使用新的oracle版本(12c-r2 ->12.2.0.1),需要从之前的oracle11g迁移到12c.所以,我们今天就先来介绍一下如何构建oracle12c的镜像 ...

  7. Nmap脚本文件分析(AMQP协议为例)

    Nmap脚本文件分析(AMQP协议为例) 一.介绍 上两篇文章 Nmap脚本引擎原理   编写自己的Nmap(NSE)脚本,分析了Nmap脚本引擎的执行过程,以及脚本文件的编写,这篇文章将以解析AMQ ...

  8. Builder模式详解及其在Android开发中的应用

    一.引言 在Android开发中,采用Builder模式的代码随处可见,比如说Android系统对话框AlertDialog的使用或者是Android中的通知栏(Notification)的使用,又比 ...

  9. Vuejs技术栈从CLI到打包上线实战全解析

    前言 本文是自己vue项目实践中的一些总结,针对Vue2及相关技术栈,实践中版本为2.3.3. 开发前须知 vue-cli 在开发前,我们要至少通读一遍vue官方文档和API(看官方文档是最重要的,胜 ...

  10. 《javascript高级程序设计》笔记三

    第三章 基本概念 任何语言的核心必然会描述这门语言最基本的工作原理.这部分内容对我们来说,读起来很乏味,甚至会产生困意,但这部分内容却是重要的!我有幸拜读<JavaScript高级程序设计> ...