runtime实现对象存储型数据库——LHDB

来源:互联网 发布:网王之知世年华 编辑:程序博客网 时间:2024/06/06 03:34

前言


 

  最近在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,如下:

 1 @interface Teacher : NSObject 2  3 @property (nonatomic,strong) NSString* name; 4  5 @property (nonatomic,assign) NSInteger age; 6  7 @property (nonatomic,strong) NSDate* updateDate; 8  9 @property (nonatomic,strong) NSData* data;10 11 @end

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

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

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

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

LHModelStateMent.m

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

 

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

NSObject+LHModel.m

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

 

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

LHSqlite.m

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

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

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

 1 - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt { 2      3     if ((!obj) || ((NSNull *)obj == [NSNull null])) { 4         sqlite3_bind_null(pStmt, idx); 5     } 6      7     else if ([obj isKindOfClass:[NSData class]]) { 8         const void *bytes = [obj bytes]; 9         if (!bytes) {10 11             bytes = "";12         }13         sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);14     }15     else if ([obj isKindOfClass:[NSDate class]]) {16         if (self.dateFormatter)17             sqlite3_bind_text(pStmt, idx, [[self.dateFormatter stringFromDate:obj] UTF8String], -1, SQLITE_STATIC);18         else19             sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String],-1,SQLITE_STATIC);20     }21     else if ([obj isKindOfClass:[NSNumber class]]) {22         23         if (strcmp([obj objCType], @encode(char)) == 0) {24             sqlite3_bind_int(pStmt, idx, [obj charValue]);25         }26         else if (strcmp([obj objCType], @encode(unsigned char)) == 0) {27             sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);28         }29         else if (strcmp([obj objCType], @encode(short)) == 0) {30             sqlite3_bind_int(pStmt, idx, [obj shortValue]);31         }32         else if (strcmp([obj objCType], @encode(unsigned short)) == 0) {33             sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);34         }35         else if (strcmp([obj objCType], @encode(int)) == 0) {36             sqlite3_bind_int(pStmt, idx, [obj intValue]);37         }38         else if (strcmp([obj objCType], @encode(unsigned int)) == 0) {39             sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);40         }41         else if (strcmp([obj objCType], @encode(long)) == 0) {42             sqlite3_bind_int64(pStmt, idx, [obj longValue]);43         }44         else if (strcmp([obj objCType], @encode(unsigned long)) == 0) {45             sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);46         }47         else if (strcmp([obj objCType], @encode(long long)) == 0) {48             sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);49         }50         else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {51             sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);52         }53         else if (strcmp([obj objCType], @encode(float)) == 0) {54             sqlite3_bind_double(pStmt, idx, [obj floatValue]);55         }56         else if (strcmp([obj objCType], @encode(double)) == 0) {57             NSLog(@"%f",[obj doubleValue]);58             sqlite3_bind_double(pStmt, idx, [obj doubleValue]);59         }60         else if (strcmp([obj objCType], @encode(BOOL)) == 0) {61             sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));62         }63         else {64             sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);65         }66     }67     else if ([obj isKindOfClass:[NSArray class]]||[obj isKindOfClass:[NSDictionary class]]) {68         @try {69             NSData* data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:nil];70             NSString* jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];71             sqlite3_bind_text(pStmt, idx, [[jsonStr description] UTF8String], -1, SQLITE_STATIC);72         }73         @catch (NSException *exception) {74             75         }76         @finally {77             78         }79         80     }else if ([obj isKindOfClass:NSClassFromString(@"UIImage")]) {81         NSData* data = UIImagePNGRepresentation(obj);82         const void *bytes = [data bytes];83         if (!bytes) {84             bytes = "";85         }86         sqlite3_bind_blob(pStmt, idx, bytes, (int)[data length], SQLITE_STATIC);87     }88     else {89         sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);90     }91 }

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

  2.  SQL插入和查询

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

  • SQL插入数据

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

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

 

NSObject+LHDB.m

1 - (void)save2 {3     LHSqlite* sqlite = [LHSqlite shareInstance];4     sqlite.sqlPath = [self dbPath];5     [sqlite executeUpdateWithSqlstring:insertString(self) parameter:[self lh_ModelToDictionary]];6 }

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

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

LHModelStateMent.m

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

 

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

NSObject+LHModel.m

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

注意的地方就是,当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>0) {            NSNumber* number = getBaseTypePropertyValue(object, objectInfo.baseTypeEcoding, objectInfo.get);            [dic setValue:number forKey:dicKey];        }    }}
 1 static NSNumber* getBaseTypePropertyValue(__unsafe_unretained NSObject* object, NSUInteger type,SEL get) 2 { 3     switch (type) { 4         case LHBaseTypeEcodingINT: 5              6             return @(((int (*)(id, SEL))(void *) objc_msgSend)(object, get)); 7              8         case LHBaseTypeEcodingLONG: 9             10             return @(((long (*)(id, SEL))(void *) objc_msgSend)(object,get));11             12         case LHBaseTypeEcodingULONG:13             14             return @(((NSUInteger(*)(id,SEL))(void*) objc_msgSend)(object,get));15             16         case LHBaseTypeEcodingFLOAT:17             18             return @(((float(*)(id,SEL))(void*) objc_msgSend)(object,get));19 20         case LHBaseTypeEcodingDOUBLE:21             22             return @(((double(*)(id,SEL))(void*) objc_msgSend)(object,get));23 24         case LHBaseTypeEcodingBOOL:25             26             return @(((BOOL(*)(id,SEL))(void*) objc_msgSend)(object,get));27             28         case LHBaseTypeEcodingCHAR:29             30            return @(((char(*)(id,SEL))(void*) objc_msgSend)(object,get));31 32         default:33             return nil;34             break;35     }36 }

 

LHObjectInfo.h

 1 //对象类信息 2 @interface LHClassInfo : NSObject 3  4 @property (nonatomic)Class cls; 5  6 @property (nonatomic)Class superClass; 7  8 @property (nonatomic)Class metaClass; 9 10 @property (nonatomic,assign) BOOL isMetaClass;11 12 @property (nonatomic,strong) NSMutableDictionary* objectInfoDic;13 14 - (instancetype)initWithClass:(Class)cls;15 16 + (BOOL)isCacheWithClass:(Class)cls;17 18 + (LHClassInfo*)classInfoWithClass:(Class)cls;19 20 - (LHObjectInfo*)objectInfoWithName:(NSString*)name;21 22 @end

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

 1 typedef NS_ENUM(NSUInteger,LHBaseTypeEcoding) { 2     LHBaseTypeEcodingUnknow, 3     LHBaseTypeEcodingINT, 4     LHBaseTypeEcodingLONG, 5     LHBaseTypeEcodingULONG, 6     LHBaseTypeEcodingCHAR, 7     LHBaseTypeEcodingFLOAT, 8     LHBaseTypeEcodingBOOL, 9     LHBaseTypeEcodingDOUBLE10 };11 12 typedef NS_ENUM(NSUInteger,LHNSTypeEcoding) {13     LHNSTypeUNknow,14     LHNSTypeNSString,15     LHNSTypeNSNumber,16     LHNSTypeNSDate,17     LHNSTypeNSData,18     LHNSTypeNSURL,19     LHNSTypeNSArray,20     LHNSTypeNSDictionary,21     LHNSTypeUIImage22 };23 24 25 //描述对象属性的结构26 @interface LHObjectInfo : NSObject27 28 @property (nonatomic) Class cls;    //当属性是OC对象时,cls记录属性对象所属类,否则为基础类型时,值为nil29 30 @property (nonatomic) objc_property_t property_t;   //属性31 32 @property (nonatomic,copy) NSString* name;  //属性名33 34 @property (nonatomic,assign) LHBaseTypeEcoding baseTypeEcoding;  //自定义基础数据类型编码35 36 @property (nonatomic,assign) LHNSTypeEcoding nsTypeEcoding; //自定义OC对象类型编码37 38 @property (nonatomic) SEL set;  //属性的setter方法39 40 @property (nonatomic) SEL get;  //属性的getter方法41 42 @property (nonatomic,copy) NSString* type; //对象类型,如:NSString,i,Q,d等等43 44 - (instancetype)initWithProperty:(objc_property_t)property;45 46 @end

LHObjectInfo.m

LHClassInfo类实现

 1 - (instancetype)initWithClass:(Class)cls 2 { 3     self = [super init]; 4     if (self) { 5         _cls = cls; 6         static dispatch_once_t onceToken; 7         dispatch_once(&onceToken, ^{ 8             objectInfoCacheDic = [NSMutableDictionary dictionary]; 9         });10         _objectInfoDic = [NSMutableDictionary dictionary];11         12         //遍历属性,为每个属性生成 LHObjectInfo 对象 并保存在类字典objectInfoDic中,注意是类13         unsigned int count;14         objc_property_t* t = class_copyPropertyList(cls, &count);15         for (int i=0; i<count; i++) {16             17             LHObjectInfo* info = [[LHObjectInfo alloc] initWithProperty:t[i]];18             [_objectInfoDic setValue:info forKey:[NSString stringWithUTF8String:property_getName(t[i])]];19         }20         free(t);21         22         //记录类名对应的类信息,并保存在静态全局缓存字典objectInfoCacheDic中23         [objectInfoCacheDic setValue:self forKey:NSStringFromClass(cls)];24     }25     return self;26 }27 28 + (BOOL)isCacheWithClass:(Class)cls29 {30     if ([objectInfoCacheDic objectForKey:NSStringFromClass(cls)]) {31         return YES;32     }33     return NO;34 }35 36 + (LHClassInfo*)classInfoWithClass:(Class)cls37 {38     return objectInfoCacheDic[NSStringFromClass(cls)];39 }

LHObjectInfo类实现

 1 - (instancetype)initWithProperty:(objc_property_t)property 2 { 3     if (property == nil) return nil; 4     self = [super init]; 5     if (self) { 6         _property_t = property; 7         _name = [NSString stringWithUTF8String:property_getName(property)]; //记录属性名 8          9         unsigned int count;10         objc_property_attribute_t* t = property_copyAttributeList(property, &count);11         //for (unsigned int i=0; i<count; i++) {12         if(count > 0){13             //源代码是一个循环,其实这里只是获取第一个属性值,也就是T,即是属性的类型,所以我这里改成了一个判断语句,将t[i] 改成 t[0]14             objc_property_attribute_t p = t[0];15             size_t len = strlen(p.value);  //假设属性是NSString,则p.name = "T",p.value = "@\"NString\"";16             if (len > 3) {17                 char name[len - 2];18                 name[len - 3] = '\0';19                 memcpy(name, p.value + 2, len - 3);20                 _cls = objc_getClass(name); //记录类21                 _type = [NSString stringWithUTF8String:name];   //记录对象类型22                 _nsTypeEcoding = nsTypeEcoding(_type);  //记录oc对象编码,目前只支持NSString,NSNumber,NSDate,NSData,NSURL,NSArray,NSDictionary,UIImage23                 //break;24             }else {25                 //基础数据类型26                 _type = [NSString stringWithUTF8String:p.value];27                 if (_type.length>1) {28                     _type = [_type substringToIndex:1];29                 }30                 if (_type.length>0) {31                     _baseTypeEcoding = baseTypeEcoding([_type characterAtIndex:0]);32                 }33                 //break;34             }35         }36         free(t);37         38         if (_name.length>0) {39             _set = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[[_name substringToIndex:1] uppercaseString],[_name substringFromIndex:1]]);40             _get = NSSelectorFromString(_name);41         }42     }43     return self;44 }

 

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

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

 

  • SQL查询数据

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

  同样,从外部调用开始:

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

 

NSObject+LHDB.m

 1 //查询记录不需要对象方法 2 + (NSArray*)selectWithPredicate:(LHPredicate*)predicate 3 { 4     LHSqlite* sqlite = [LHSqlite shareInstance]; 5     sqlite.sqlPath = [self dbPath]; 6     NSArray* array = [sqlite executeQueryWithSqlstring:selectString(self, predicate)]; 7     NSMutableArray* resultArray = [NSMutableArray array]; 8     for (NSDictionary* dic in array) { 9         [resultArray addObject:[self lh_ModelWithDictionary:dic]];10     }11     return resultArray;12 }

 

LHModelStateMent.m

 1 NSString* selectString(Class modelClass,LHPredicate* predicate) 2 { 3     NSMutableString* selectStr = [NSMutableString stringWithString:SELECT_HEADER]; 4     [selectStr appendString:NSStringFromClass(modelClass)]; 5     if (predicate.predicateFormat) { 6         [selectStr appendFormat:@" WHERE %@",predicate.predicateFormat]; 7     } 8     if (predicate.sortString) { 9         [selectStr appendFormat:@" ORDER BY %@",predicate.sortString];10     }11     return selectStr;12 }

 

LHSqlite.m

 1 - (NSArray*)executeQueryWithSqlstring:(NSString*)sqlString 2 { 3     Lock; 4     NSArray* resultArray = 0x00; 5     if ([self openDB]) { 6         sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString]; 7         if (stmt) { 8             NSMutableArray* dataSource = [NSMutableArray array]; 9             int count = sqlite3_column_count(stmt);10             while (sqlite3_step(stmt) == SQLITE_ROW) {11                 NSMutableDictionary* dataDic = [NSMutableDictionary dictionary];12                 for (int i=0; i<count; i++) {13                     int type = sqlite3_column_type(stmt, i);14                     NSString* propertyName = [NSString stringWithUTF8String:sqlite3_column_name(stmt, i)];15                     NSObject* value = dataWithDataType(type, stmt, i);16                     [dataDic setValue:value forKey:propertyName];17                 }18                 [dataSource addObject:dataDic];19             }20             resultArray = dataSource;21         }22     }23     sqlite3_close(_db);24     UnLock;25     return resultArray;26 }

 

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

LHSqlite.m

 1 static NSObject* dataWithDataType(int type,sqlite3_stmt * statement,int index) 2 { 3     if (type == SQLITE_INTEGER) { 4         int value = sqlite3_column_int(statement, index); 5         return [NSNumber numberWithInt:value]; 6     }else if (type == SQLITE_FLOAT) { 7         float value = sqlite3_column_double(statement, index); 8         return [NSNumber numberWithFloat:value]; 9     }else if (type == SQLITE_BLOB) {10         const void *value = sqlite3_column_blob(statement, index);11         int bytes = sqlite3_column_bytes(statement, index);12         return [NSData dataWithBytes:value length:bytes];13     }else if (type == SQLITE_NULL) {14         return nil;15     }else if (type == SQLITE_TEXT) {16         return [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, index)];17     }else {18         return nil;19     }20 }

 

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

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

 

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

NSObject+LHModel.m

 1 static void ModelSetValueToProperty(const void *key, const void *value, void *context) 2 { 3     ModelSetContext* modelContext = context; 4     NSString* dicKey = (__bridge NSString *)(key); 5     id dicValue = (__bridge id)(value); 6     LHObjectInfo* objectInfo = [((__bridge LHClassInfo*)modelContext->classInfo) objectInfoWithName:dicKey]; //根据属性名获取属性信息结构 7     NSObject* object = (__bridge NSObject*)modelContext->model; 8     if (objectInfo) { 9         if (objectInfo.cls) {10             setNSTypePropertyValue(object, dicValue, objectInfo.nsTypeEcoding, objectInfo.set);11             12         }else if (objectInfo.type.length>0) {13             NSNumber* number = numberWithValue(dicValue);14             setBaseTypePropertyValue(object, number, objectInfo.baseTypeEcoding,objectInfo.set);15         }16     }17 }
 1 static NSNumber* numberWithValue(__unsafe_unretained id value) 2 { 3     if (!value) { 4         return nil; 5     } 6     if ([value isKindOfClass:[NSNumber class]]) return value; 7     if ([value isKindOfClass:[NSString class]]) { 8         if ([value containsString:@"."]) { 9             10             const char *cstring = ((NSString *)value).UTF8String;11             if (!cstring) return nil;12             double num = atof(cstring);13             if (isnan(num) || isinf(num)) return nil; //判断浮点数是否是非数字或者无限大14             return @(num);15         }else {16             const char *cstring = ((NSString*)value).UTF8String;17             if (!cstring) return nil;18             NSNumber* number = @(atoll(cstring));19             if (!atoll(cstring)) {20                 number = [NSNumber numberWithChar:*(cstring+0)];21             }22             return number;23         }24     }25     return nil;26     27 }
 1 static void setBaseTypePropertyValue(__unsafe_unretained NSObject* object,__unsafe_unretained NSNumber* value, NSUInteger type,SEL set) 2 { 3     switch (type) { 4         case LHBaseTypeEcodingINT: 5             ((void (*)(id, SEL, int))(void *) objc_msgSend)(object, set, value.intValue); 6             break; 7          8         case LHBaseTypeEcodingLONG: 9             ((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.integerValue);10             break;11         case LHBaseTypeEcodingULONG:12             ((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.unsignedIntegerValue);13             break;14             15         case LHBaseTypeEcodingFLOAT:16             ((void(*)(id,SEL,float))(void*) objc_msgSend)(object,set,value.floatValue);17             break;18         case LHBaseTypeEcodingDOUBLE:19             ((void(*)(id,SEL,double))(void*) objc_msgSend)(object,set,value.doubleValue);20             break;21         case LHBaseTypeEcodingBOOL:22             ((void(*)(id,SEL,BOOL))(void*) objc_msgSend)(object,set,value.boolValue);23             break;24             25         case LHBaseTypeEcodingCHAR:26             ((void(*)(id,SEL,char))(void*) objc_msgSend)(object,set,value.charValue);27             break;28         default:29             ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,nil);30             break;31     }32 }33 34 static void setNSTypePropertyValue(__unsafe_unretained id object,__unsafe_unretained id value,LHNSTypeEcoding typeEcoding,SEL set)35 {36     switch (typeEcoding) {37         case LHNSTypeUNknow:38             ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);39             break;40         41         case LHNSTypeNSString:42             //将其它类型转成nsstring类型43             if ([value isKindOfClass:[NSString class]]) {44                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);45             }else if ([value isKindOfClass:[NSNumber class]]) {46                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[value stringValue]);47             }else if ([value isKindOfClass:[NSData class]]) {48                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);49             }else if ([value isKindOfClass:[NSDate class]]) {50                 ((void(*)(id,SEL,NSString*))(void*) objc_msgSend)(object,set,stringFormDate(value));51             }else52                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);53             break;54             55         case LHNSTypeNSNumber:56             ((void(*)(id,SEL,NSNumber*))(void*) objc_msgSend)(object,set,numberWithValue(value));57             break;58             59         case LHNSTypeNSDate:60             if ([value isKindOfClass:[NSDate class]]) {61                 ((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,value);62             }else if ([value isKindOfClass:[NSString class]]) {63                 ((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(value));64             }else if ([value isKindOfClass:[NSData class]]) {65                 NSString* dateStr = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding];66                 ((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(dateStr));67             }else68                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);69             break;70             71         case LHNSTypeNSData:72             ((void(*)(id,SEL,NSData*))(void*) objc_msgSend)(object,set,dataFromObject(value));73             break;74             75         case LHNSTypeNSURL:76             ((void(*)(id,SEL,NSURL*))(void*) objc_msgSend)(object,set,urlFromObject(value));77             break;78             79         case LHNSTypeNSArray:80             ((void(*)(id,SEL,NSArray*))(void*) objc_msgSend)(object,set,arrayFromObject(value));81             break;82             83         case LHNSTypeNSDictionary:84             ((void(*)(id,SEL,NSDictionary*))(void*) objc_msgSend)(object,set,dicFromObject(value));85             break;86             87         case LHNSTypeUIImage:88             ((void(*)(id,SEL,UIImage*))(void*) objc_msgSend)(object,set,imageFromObject(value));89             break;90         91         default:92             break;93     }94 }

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

 

总结


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

 

 

 

 

 

 

原创粉丝点击