Runtime系列9之-Runtime的应用(二)
RunTime获取成员变量/属性/方法/协议列表
-
代码如下:
unsigned int count = 0; //获取属性列表 + (void)getProperties { unsigned int count = 0; // 拷贝出所有的成员变量属性列表 objc_property_t *properties = class_copyPropertyList([UITextField class], &count); for (int i = 0; i<count; i++) { // 取出属性 objc_property_t property = properties[i]; // 打印属性名字 NSLog(@"%s <----> %s", property_getName(property), property_getAttributes(property)); } free(properties); } //获取方法列表 + (void)getMethods { unsigned int count = 0; // 拷贝出所有的方法列表 Method *methods = class_copyMethodList([UITextField class], &count); for (int i = 0; i<count; i++) { // 取出成员变量 Method method = methods[i]; // 打印该类的方法 NSLog(@"%@ %p", NSStringFromSelector(method_getName(method)), method_getImplementation(method)); } // 释放 free(methods); } //获取成员变量列表 + (void)getIvars { unsigned int count = 0; // 拷贝出所有的成员变量列表 Ivar *ivars = class_copyIvarList([UITextField class], &count); for (int i = 0; i<count; i++) { // 取出成员变量 // Ivar ivar = *(ivars + i); Ivar ivar = ivars[i]; // 打印成员变量名字 NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar)); } // 释放 free(ivars); } //获取协议列表 + (void)getProtocols { //获取协议列表 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([UITextField class], &count); for (unsigned int i = 0; i<count; i++) { Protocol *myProtocal = protocolList[i]; const char *protocolName = protocol_getName(myProtocal); NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]); /* //oc字符串与C语言字符串之间的转换 NSString---->char * [@"dfafds" UTF8String]; char *---->NSString [NSString stringWithUTF8String:protocolName] */ } }
Runtime字典转模型
-
为了方便以后重用,这里通过给NSObject添加分类,声明并实现使用RunTime字典转模型的类方法。
+ (instancetype)modelWithDict:(NSDictionary *)dict
- 首先来看一下KVC字典转模型和RunTime字典转模型的区别:
- KVC: KVC字典转模型实现原理是遍历字典中所有Key,然后去模型中查找相对应的属性名,要求属性名与Key必须一一对应,字典中所有key必须在模型中存在。
- RunTime: RunTime字典转模型实现原理是遍历模型中的所有属性名,然后去字典查找相对应的Key,也就是以模型为准,模型中有哪些属性,就去字典中找那些属性。
- RunTime字典转模型的优点: 当服务器返回的数据过多,而我们只使用其中很少一部分时,没有用的属性就没有必要定义成属性浪费不必要的资源。只保存最有用的属性即可。
- RunTime字典转模型过程
- 首先需要了解,属性定义在类里面,那么类里面就有一个属性列表,属性列表以数组的形式存在,根据属性列表就可以获得类里面的所有属性名,所以遍历属性列表,也就可以遍历模型中的所有属性名。
- 所以RunTime字典转模型过程就很清晰了。
-
创建模型对象
id objc = [[self alloc] init];
-
使用class_copyIvarList方法拷贝成员属性列表
unsigned int count = 0; Ivar *ivarList = class_copyIvarList(self, &count);
- 参数一:
__unsafe_unretained Class cls :
获取哪个类的成员属性列表。这里是self,因为谁调用分类中类方法,谁就是self。 - 参数二:
unsigned int *outCount :
无符号int型指针,这里创建unsigned int型count,&count就是他的地址,保证在方法中可以拿到count的地址为count赋值。传出来的值为成员属性总数。 - 返回值:
Ivar * :
返回的是一个Ivar类型的指针 。指针默认指向的是数组的第0个元素,指针+1会向高地址移动一个Ivar单位的字节,也就是指向第一个元素。Ivar表示成员属性。
- 参数一:
-
遍历成员属性列表,获得属性列表
for (int i = 0 ; i < count; i++) { // 获取成员属性 Ivar ivar = ivarList[i]; }
-
使用
ivar_getName(ivar)
获得成员属性名,因为成员属性名返回的是C语言字符串,将其转化成OC字符串,通过ivar_getTypeEncoding(ivar)
可以获得成员属性类型。NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
-
因为获得的是成员属性名,是带_的成员属性,所以需要将下划线去掉,获得属性名,也就是字典的key。
// 获取key NSString *key = [propertyName substringFromIndex:1];
-
获取字典中key对应的Value。
// 获取字典的value id value = dict[key];
-
给模型属性赋值,并将模型返回
if (value) { // KVC赋值:不能传空 [objc setValue:value forKey:key]; } return objc;
-
- 至此已成功将字典转为模型。
- 代码如下:
#import <Foundation/Foundation.h> @interface NSObject (Extension) + (instancetype)modelWithDict:(NSDictionary *)dict; @end #import "NSObject+Extension.h" #import <objc/runtime.h> @implementation NSObject (Extension) +(instancetype)modelWithDict:(NSDictionary *)dict{ id objc = [[self alloc] init]; unsigned int count = 0; Ivar *ivarList = class_copyIvarList(self, &count); for (int i = 0 ; i < count; i++) { // 获取成员属性 Ivar ivar = ivarList[i]; NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)]; NSString *key = [propertyName substringFromIndex:1]; id value = dict[key]; if (value) { // KVC赋值:不能传空 [objc setValue:value forKey:key]; } } return objc; } @end
Runtime字典转模型二级转换
- 在开发过程中经常用到模型嵌套,也就是模型中还有一个模型,这里尝试用RunTime进行模型的二级转换,实现思路其实比较简单清晰。
-
首先获得一级模型中的成员属性的类型
// 成员属性类型 NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
- 判断当一级字典中的value是字典,并且一级模型中的成员属性类型不是NSDictionary的时候才需要进行二级转化。
- 首先一级字典中的value是字典才进行转化是必须的,因为我们通常将字典转化为模型
- 其次,一级模型的成员属性类型不是系统类,说明成员属性是我们自定义的类,也就是要转化的二级模型。而当一级模型的成员属性类型就是NSDictionary的话就表明,我们本就想让成员属性是一个字典,不需要进行模型的转换。
//拿到字典中每个key对应的值 id value = dict[key]; //判断value是不是NSDictionary类,而且判断一级模型的对应属性不是系统自带类 if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) { // 进行二级转换。 }
-
获取要转换的模型类型,这里需要对
propertyType
成员属性类型做一些处理,因为propertyType
返回给我们成员属性类型的是@\"Mode\"
,我们需要对他进行截取为Mode。这里需要注意的是\只是转义符,不占位。// @\"Mode\"去掉前面的@\" NSRange range = [propertyType rangeOfString:@"\""]; propertyType = [propertyType substringFromIndex:range.location + range.length]; // Mode\"去掉后面的\" range = [propertyType rangeOfString:@"\""]; propertyType = [propertyType substringToIndex:range.location];
-
获取需要转换类的类对象,将字符串转化为类名。
Class modelClass = NSClassFromString(propertyType);
-
判断如果类名不为空则调用分类的modelWithDict方法,传value字典,进行二级模型转换,返回二级模型在赋值给一级模型key(此时key对应的类就是二级模型的类)对应的value。
if (modelClass) { value = [modelClass modelWithDict:value]; }
- 总结:
- 我们通过判断一级字典key对应的value是字典并且该key在一级模型中对应的属性类为自定义类
- 然后将value字典转化为二级模型返回,并重新赋值给(id类)value
- 最后给一级模型中相对应的key赋值模型value即可完成二级字典对模型的转换。
- 最后附上二级转换的完整方法:
+ (instancetype)modelWithDict:(NSDictionary *)dict{
// 1.创建对应类的对象
id objc = [[self alloc] init];
// count:成员属性总数
unsigned int count = 0;
// 获得成员属性列表和成员属性数量
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0 ; i < count; i++) {
// 获取成员属性
Ivar ivar = ivarList[i];
// 获取成员名
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取key
NSString *key = [propertyName substringFromIndex:1];
// 获取字典的value key:属性名 value:字典的值
id value = dict[key];
// 获取成员属性类型
NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 二级转换
// value值是字典并且成员属性的类型不是字典,才需要转换成模型
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
// 进行二级转换
// 获取二级模型类型进行字符串截取,转换为类名
NSRange range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringFromIndex:range.location + range.length];
range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringToIndex:range.location];
// 获取需要转换类的类对象
Class modelClass = NSClassFromString(propertyType);
// 如果类名不为空则进行二级转换
if (modelClass) {
// 返回二级模型赋值给value
value = [modelClass modelWithDict:value];
}
}
if (value) {
// KVC赋值:不能传空
[objc setValue:value forKey:key];
}
}
// 返回模型
return objc;
}