我们应该经常能碰到这样的场景:在网络数据回来之后需要将网络数据转化成实体(model),通常的做法是利用kvc来为实体赋值,或者利用现在已有的MJExtention、YYModel来做。
今天以我自己写的HBEntity为例解析下如何利用runtime来为实体赋值。
HBEntity简介:
HBEntity是一个利用runtime来将NSArray或NSDictionary对象转化成自定义实体的工具,支持多实体嵌套、免除了MJExtention和YYModel中白名单和黑名单的概念,同时还支持key与属性名之间的适配。
实现思路:
HBEntity实现将NSArray或NSDictionary对象转化成自定义实体的思路比较简单。我们把问题转换一下,上学的时候我们经常会遇到解y=f(x)的问题,现在把x与我们的NSArray和NSDictionary对象对应,y与我们自定义实体对应,f函数就是我们的HBEntity,我们来捋一下我们的已知条件,我们已知x和y,要求f! WTF,怎么解 。。 从x、y去推f,这个f可能有很多种啊!!我比较喜欢倒推。既然我们是要给自定义实体赋值,那我们需要知道这个实体有多少属性,每个属性是什么类型,有没有自定义getter 和setter方法等。runtime在这个时候就派上用场了。
在拿到实体所有的属性之后剩下的就是用NSDictionary或者NSArray对象(为了方便以下都简称为“对象”,我们自定义的类称为实体)赋值了。所以在这里我也不需要去过滤对象中哪些值是我不想参与赋值的(也就是blacklist),对于我来说没有blacklist和whitelist的区分。
总结起来为:
1.解析实体的属性
2.用对象的值为实体的属性赋值。
下面我们边看代码吧边bb吧。
1.解析实体的属性——runtime
解析实体的属性时我们会用到 class_copyPropertyList方法,
OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
该方法会将cls类里所有的属性都放到一个数组里返回,outCount返回的是属性的数量,如果该cls有父类,class_copyPropertyList方法并不会返回父类的属性,当然也不会将父类属性的个数计算到outCount内。这里需要说明的是objc_property_t是一个objc_property的结构体指针,objc_property有name 和 attributes,如果我们声明了一个属性
@property (nonatomic,copy,readonly)NSString *test;
这里test就是objc_property的name,可以通过property_getName来获取,而attributes可以通过property_getAttributes来获取,attributes是我们声明的属性(nonatomic,copy,readonly等)的描述,我们先来看下一个测试Demo返回的attribues,再来苹果文档里是怎么描述这一块的。
T@“NSString”,C,N,V_entityName这一串就是attributes
从上图可以看出,每个attribute都有name和value两部分组成,我们可以通过property_copyAttributeList来获得某个属性的atrributeList,每一项是objc_property_attribure_t类型,objc_property_attribure_t类型如下:
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
那具体T、C、N这些符号代表的是什么意思呢?他的值又表示的是什么呢?我们求助一下官方文档。
上面是官方文档对T、C、N等做出的解答。下面再来看看他们的值代表的什么意思。
这里是官方文档的部分截图,可以看出我们可以通过T来知道该属性的类型。
官方文档里展示的例子并没有包含所有的类型,在我的HBEntity里有补充(下载地址:https://github.com/knighthb/HBEntity),感兴趣的同学可以下载下来看看。
基本上通过以上的几个方法就能确定一个类里有多少属性(不含父类的属性)、每个属性是详细信息等。
2.利用对象为实体赋值
这里基本上就是键值对映射,如果在适配器里为key和属性名做了适配,那么在赋值时将会把key对应的value赋值给对应的属性。
对于普通的类(属性里没有自定义类、没有数组等的类)这些工作已经足够了,对于比较棘手的我们就需要进行一下处理,比如对于属性是NSArray或NSMutableArray的类赋值时我们需要知道NSArray里装的是什么,也许有同学会说,这个简单,我们声明属性的时候声明为NSArray这样的形式就知道它是NSString的啦,乍一看是的,但是你用runtime根本拿不出来,因为这种写法是编译时的,只是告诉编译器array里装的是NSString,所以在runtime根本找不到。因此这块避免不了MJExtention或YYModel里指定类名的这类操作,如果有同学有好的解决方法请不吝赐教。
另外还有一个棘手的问题就是数值类型的处理,需要装箱拆箱过程,一直没有想出出去硬编码的方法,如果有同学解决过类似的问题也请不吝赐教。
最后上点Demo:
//HBTestPerson.h
#import "HBEntity.h"
#import "HBTestEntity.h"
@interface HBTestPerson : HBEntity
@property (nonatomic , copy)NSString * entityName;
@property (nonatomic , strong) NSString *entityNum;
@property (nonatomic , strong,setter=setEntityAge2:) HBTestEntity *testEntity;
@property (nonatomic , strong) NSMutableArray* testEntities;
@property (nonatomic , assign) BOOL boolTest;
@end
//HBTestPerson.m
#import "HBTestPerson.h"
@implementation HBTestPerson
- (NSDictionary *)hb_transferDic {
return @{@"entityname":@"entityName",
@"entitynum":@"entityNum"};
}
+ (NSDictionary *)hb_objectClassForKeyDic {
return @{@"testEntities":[HBTestEntity class]};
}
- (void)setEntityAge2:(HBTestEntity *)entityAge {
_testEntity = entityAge;
}
@end
//HBTestEntity.h
#import "HBEntity.h"
@interface HBTestEntity : HBEntity
@property (nonatomic , copy) NSString * name;
@property (nonatomic , copy) NSNumber * age;
@end
//HBTestEntity.m
#import "HBTestEntity.h"
@implementation HBTestEntity
- (void)setName:(NSString *)name {
_name = name;
}
@end
//HBArrayTestEntity.h
#import "HBEntity.h"
@interface HBArrayTestEntity : HBEntity
@property (nonatomic , strong) NSMutableArray * array;
@end
//HBArrayTestEntity.m
#import "HBArrayTestEntity.h"
#import "HBTestEntity.h"
@implementation HBArrayTestEntity
+ (NSDictionary *)hb_objectClassForKeyDic {
return @{@"array":[HBTestEntity class]};
}
@end
上面是几个测试类,下面是测试代码
NSDictionary * entityDic = @{@"entityname":@"hehe",
@"entitynum":@"1",
@"testEntity":@{@"name":@"xiaoming",
@"age":@(34)
},
@"testEntities":@[@{@"name":@"xiaoming",
@"age":@(34)
},
@{@"name":@"xiaoming",
@"age":@(34)
},
@{@"name":@"xiaoming",
@"age":@(34)
}]};
HBTestPerson * entity = [HBTestPerson transferEntityWithDic:entityDic];
NSArray * entityArray = @[@{@"name":@"xiaoming",
@"age":@(34)
},
@{@"name":@"xiaoming",
@"age":@(34)
},
@{@"name":@"xiaoming",
@"age":@(34)
}];
HBArrayTestEntity * arrayTestEntity = [HBArrayTestEntity transferEntityWithObject:entityArray];
结果如下:
总结:
不要脸的取了个总结,主要是为了贴下github的地址
github地址:https://github.com/knighthb/HBEntity
或者 git clone https://github.com/knighthb/HBEntity.git
也可以通过Pod 来安装 HBEntity玩耍
pod 'HBEntity'
希望通过解析HBEntity能让大家对runtime能有不一样的认识,如果大家在玩耍的过程中遇到了问题或者发现了bug请及时联系我
QQ:513179531
微信:knight_hb
或者在底下留言 ^ ^