前言
我们在iOS开发中,一般会使用MVC或者MVVM等模式。当我们从接口中拿到数据时,我们需要把数据转成模型使用。下面我就带大家一起用runtime一步一步的来完成这个转换框架.(比较简单的model不用runtime也可以的) .
git地址 https://github.com/guochaoshun/RootModel
1、先写一个简单的字典到模型的转换
先来最简单的 , 比如服务器给的数据是这种结构
// 没有嵌套字典,没有数组,都是平级的json
{
"star" : 0,
"pageIndex" : 0,
"pageSize" : 20,
"totalCount" : 5,
"pageTotalNum" : 1
}
//字典转模型
+ (instancetype)initWithDictionary:(NSDictionary *)dic
{
id myObj = [[self alloc] init];
unsigned int outCount;
//获取类中的所有成员属性
objc_property_t *arrPropertys = class_copyPropertyList([self class], &outCount);
for (NSInteger i = 0; i < outCount; i ++) {
objc_property_t property = arrPropertys[i];
//获取属性名字符串
//model中的属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
id propertyValue = dic[propertyName];
if (propertyValue != nil) {
[myObj setValue:propertyValue forKey:propertyName];
}
}
free(arrPropertys);
return myObj;
}
@end
当然这种简单的结构 , 可以不需要runtime出马 , 比如这样写,同样可以完成需求,
[self setValuesForKeysWithDictionary:dic];
这个方法也要有,防止有多给的字段
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
2、模型中嵌套有模型
现在难度增加 ,服务器给出了这种结构 , 字典中嵌套了字典
{
"len" : 0,
"resultMsg" : "OK",
"success" : true,
"resultCode" : 200,
"page" : {
"star" : 0,
"pageIndex" : 0,
"pageSize" : 20,
"totalCount" : 5,
"pageTotalNum" : 1
}
}
让model都继承rootModel, 给NSObject加类别也可以, 加类别 好处是 现有的工程几乎不用动,坏处是,以后再扩展不太方便 , 继承rootModel好处是 扩展容易 , 比较适合一个新的工程 .
- (instancetype)initWithDic:(NSDictionary *)dic {
self = [super init];
if (self) {
// [self setValuesForKeysWithDictionary:dic];
//得到当前class的所有属性
uint count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
//循环并用KVC得到每个属性的值
for (int i = 0; i<count; i++) {
objc_property_t property = properties[i];
NSString *name = @(property_getName(property));
id value = [dic valueForKey:name];
NSString *type = @(property_getAttributes(property));
NSLog(@"name = %@ type = %@ value = %@",name,type,value);
// 判断是否这个属性是RootModel的子类 , 如果是 递归的调用赋值
if ([type containsString:@"\""]) {
NSString * className = [type componentsSeparatedByString:@"\""][1];
Class modelClass = NSClassFromString(className) ;
BOOL isRootModelClass = [modelClass isKindOfClass: object_getClass([RootModel class])] ;
if (isRootModelClass) {
RootModel * subModel = [[modelClass alloc]initWithDic:value] ;
[self setValue:subModel forKey:name];
continue ;
}
}
[self setValue:value forKey:name];
}
//释放
free(properties);
}
return self;
}
在类中的声明为
- T@“NSNumber” 标记了属于什么类型
- N 表示属性中 nonmatic
- R 不可变,表示属性中readonly,
- C 表示属性中copy
- & 表示属性中strong
- W 表示属性中weak
- V_name 去掉V_,name就是变量名
通过对type进行处理就可以获得属性的类型。从而进行下一步处理。
注意点:class_copyPropertyList返回的仅仅是对象类的属性,class_copyIvarList返回类的所有属性和变量,在swift中如let a: Int? 是无法通过class_copyPropertyList返回的。
3、处理模型中有数组属性的情况
{
"len" : 0,
"resultMsg" : "OK",
"success" : true,
"resultCode" : 200,
"page" : {
"star" : 0,
"pageIndex" : 0,
"pageSize" : 20,
"totalCount" : 5,
"pageTotalNum" : 1
}
resultInfo = (
{
collectCount = 0;
diaryFabulous = 1;
followYn = 0;
},
{
collectCount = 0;
diaryFabulous = 3;
followYn = 0;
}
);
}
属性写的时候标记好RootModel的类型 , 但是 获取到的类型只有 T@“NSArray” , 这种方式无效 . 只要换一种方式了 .
@property (nonatomic,strong) NSArray <Person *> * resultInfo ;
想到需要一个字典来告诉RootModel , 这个json中的数组里面的子类的名字 , 于是计上心头 , 写一个类属性 , 来做这个映射, RootModel中是一个空字典 , 到真正需要映射的子类中重写这个方法 , key就是json中key , value就是RootModel的子类名
RootModel.h
/// json中嵌套了数组,数组中是一个个的RootModel子类,需要重写这个,把json中的key和model的子类名字映射
@property (nonatomic,readonly,class,strong) NSDictionary * arraryType ;
RootModel.m
// 这里上面都没做,主要防止崩溃的,真正的映射工作协作具体的子类中
+ (NSDictionary *)arraryType{
return @{};
}
// 某个子类 , json中的key和model类型映射 , PersonModel是RootModel的子类
+ (NSDictionary *)arraryType{
return @{
@"resultInfo":@"PersonModel"
};
}
然后增加了一块来处理这个NSArray
- (instancetype)initWithDic:(NSDictionary *)dic {
self = [super init];
if (self) {
// 方式1 , 简单,但是不能处理嵌套
// [self setValuesForKeysWithDictionary:dic];
// 方式2 ,
//得到当前class的所有属性
uint count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
//循环并用KVC得到每个属性的值
for (int i = 0; i<count; i++) {
objc_property_t property = properties[i];
NSString *name = @(property_getName(property));
id value = [dic valueForKey:name];
NSString *type = @(property_getAttributes(property));
NSLog(@"name = %@ type = %@ value = %@",name,type,value);
if ([type containsString:@"\""]) {
NSString * className = [type componentsSeparatedByString:@"\""][1];
// 处理model中嵌套model
Class modelClass = NSClassFromString(className) ;
BOOL isRootModelClass = [modelClass isKindOfClass: object_getClass([RootModel class])] ;
if (isRootModelClass) {
RootModel * subModel = [[modelClass alloc]initWithDic:value] ;
[self setValue:subModel forKey:name];
continue ;
}
// 处理model中嵌套数组
if ([className isEqualToString:@"NSArray"]) {
NSString * modelClassName = [[self class] jsonArraryType][name] ;
modelClass = NSClassFromString(modelClassName) ;
BOOL isRootModelClass = [modelClass isKindOfClass: object_getClass([RootModel class])] ;
if (isRootModelClass) {
NSArray * subModelArray = [modelClass modelArrayWithDicArray:value] ;
[self setValue:subModelArray forKey:name];
continue ;
}
}
}
[self setValue:value forKey:name];
}
//释放
free(properties);
}
return self;
}
4、字典中包含一些iOS不能用的字段
首先,尽量让服务器的命名规范一点, 然后直接使用json的名字作为属性的名字 ,这是最好的情况 , 但是往往天不遂人愿 , 接受一个旧的项目,服务器返回的key就叫id, 虽然我们也可以用id接受,但是用起来总是怪怪的,参照第三步的思路 ,
RootModel.h中
/// 属性名字映射到json中的Key , 比如属性的名字叫userID , 服务返回叫id , @{@"userID":@"id"}
@property (nonatomic,readonly,class,strong) NSDictionary * propertyNameToJsonKey ;
RootModel.m中
+ (NSDictionary *)propertyNameToJsonKey{
return @{};
}
RootModel的子类进行重写 , key,属性名字 , value,json中的原始名字
+ (NSDictionary *)propertyNameToJsonKey{
return @{
@"isSuccess":@"success"
};
}
- (instancetype)initWithDic:(NSDictionary *)dic {
self = [super init];
if (self) {
// 方式1 , 简单,但是不能处理嵌套
// [self setValuesForKeysWithDictionary:dic];
// 方式2 ,
//得到当前class的所有属性
uint count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
//循环并用KVC得到每个属性的值
for (int i = 0; i<count; i++) {
objc_property_t property = properties[i];
NSString *propertyName = @(property_getName(property));
id value = [dic valueForKey:propertyName];
// 处理value , 如果propertyNameToJsonKey有值,
NSString * jsonKey = [[self class]propertyNameToJsonKey][propertyName] ;
if (jsonKey != nil) {
value = [dic valueForKey:jsonKey];
}
NSString *type = @(property_getAttributes(property));
NSLog(@"name = %@ type = %@ value = %@",propertyName,type,value);
if ([type containsString:@"\""]) {
NSString * className = [type componentsSeparatedByString:@"\""][1];
// 处理model中嵌套model
Class modelClass = NSClassFromString(className) ;
BOOL isRootModelClass = [modelClass isKindOfClass: object_getClass([RootModel class])] ;
if (isRootModelClass) {
RootModel * subModel = [[modelClass alloc]initWithDic:value] ;
[self setValue:subModel forKey:propertyName];
continue ;
}
// 处理model中嵌套数组
if ([className isEqualToString:@"NSArray"]) {
NSString * modelClassName = [[self class] jsonArraryType][propertyName] ;
modelClass = NSClassFromString(modelClassName) ;
BOOL isRootModelClass = [modelClass isKindOfClass: object_getClass([RootModel class])] ;
if (isRootModelClass) {
NSArray * subModelArray = [modelClass modelArrayWithDicArray:value] ;
[self setValue:subModelArray forKey:propertyName];
continue ;
}
}
}
[self setValue:value forKey:propertyName];
}
//释放
free(properties);
}
return self;
}
到此,基本完成了字典转模型的功能。
5 . 舒服的打印RootModel
但是还有一点 , 直接打印log 的话,信息很少 , 看起来不舒服 , 所以重写RootModel的description,是日志的数据更舒服点.
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p> -- %@",[self class],self,[self modelToDictionary]];
}
/// 把一个RootModel还原成成一个字典 , 主要为了description使用
- (NSDictionary *)modelToDictionary {
//初始化一个字典
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
//得到当前class的所有属性
uint count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
//循环并用KVC得到每个属性的值
for (int i = 0; i<count; i++) {
objc_property_t property = properties[i];
NSString *name = @(property_getName(property));
id value = [self valueForKey:name]?:@"nil";//默认值为nil字符串
// model中嵌套了子model
if ([value isKindOfClass:[RootModel class]]) {
RootModel * subModel = value ;
//递归调用子model , 装载到字典里
[dictionary setObject:[subModel modelToDictionary] forKey:name];
continue ;
}
// model中嵌套了array , array中都是RootModel
if ([value isKindOfClass:[NSArray class]]) {
NSArray * subArray = value ;
NSMutableArray * temp = [[NSMutableArray alloc]initWithCapacity:subArray.count];
for (RootModel * subModel in subArray) {
[temp addObject:[subModel modelToDictionary]];
}
[dictionary setObject:temp forKey:name];
continue ;
}
//普通属性装载到字典里
[dictionary setObject:value forKey:name];
}
//释放
free(properties);
//return
return dictionary;
}
好了 , 完成 , 博客排版不好 , ,想要看的舒服 , 嘿嘿 . git地址 https://github.com/guochaoshun/RootModel
如果想在你的工程中用 , 直接找到RootModel.h 和 .m文件,拖入到工程就行了