最简单的 json或字典 转 model

前言

我们在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文件,拖入到工程就行了

猜你喜欢

转载自blog.csdn.net/u014600626/article/details/54906559
今日推荐