runtime实现对象存储型数据库——LHDB
前言
最近在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对数据库的管理
- 创建数据库表
先声明了一个模型类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)createTable 2 { 3 LHSqlite* sqlite = [LHSqlite shareInstance]; 4 sqlite.sqlPath = [self dbPath]; 5 [sqlite executeUpdateWithSqlstring:createTableString(self) parameter:nil]; 6 }
//数据库路径
1 + (NSString*)dbPath 2 { 3 if ([LHDBPath instanceManagerWith:nil].dbPath.length == 0) { 4 return DatabasePath; 5 }else 6 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,NSArray 22 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 }else 10 return nil; 11 }else 12 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 }else 14 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 else 19 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)save 2 { 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 }else 19 //这里记录类信息,并缓存类信息 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 LHBaseTypeEcodingDOUBLE 10 }; 11 12 typedef NS_ENUM(NSUInteger,LHNSTypeEcoding) { 13 LHNSTypeUNknow, 14 LHNSTypeNSString, 15 LHNSTypeNSNumber, 16 LHNSTypeNSDate, 17 LHNSTypeNSData, 18 LHNSTypeNSURL, 19 LHNSTypeNSArray, 20 LHNSTypeNSDictionary, 21 LHNSTypeUIImage 22 }; 23 24 25 //描述对象属性的结构 26 @interface LHObjectInfo : NSObject 27 28 @property (nonatomic) Class cls; //当属性是OC对象时,cls记录属性对象所属类,否则为基础类型时,值为nil 29 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)cls 29 { 30 if ([objectInfoCacheDic objectForKey:NSStringFromClass(cls)]) { 31 return YES; 32 } 33 return NO; 34 } 35 36 + (LHClassInfo*)classInfoWithClass:(Class)cls 37 { 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,UIImage 23 //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 }else 12 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 }else 52 ((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 }else 68 ((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,必然要应对这样复杂的使用环境,此外,在阅读源码的过程中,我也自己改动了一些方法实现,可能我觉得有些实现还不够精简,不过它作为学习拓展,我觉得还是一个比较不错的开源代码的,就这样了^-^。