前言
最近在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] = '