构建iOS-Model层(三)嵌套解析

接上,前两篇:

构建iOS Model层(一)最简单的实现Model解析

构建iOS-Model层(二)类型解析

嵌套

所谓嵌套,分两种情况:

  1. 字典里面包含字典,该字典可以转换为模型;

    NSDictionary *dic = @{
    ​ @”name”:@”wenghengcong”,
    ​ @”dog”:@{
    ​ @”nickName”:@”John”,
    ​ @”furColor”:@”blue”
    ​ }
    ​ };

  2. 字典里面包含数组,该数组里面又包含字典,而数组里面包含的字典可以转换为模型。

    NSDictionary *dic = @{
    ​ @”name”:@”wenghengcong”,
    ​ @”books”:@[
    ​ @{@”title”:@”Book1”,@”price”:@”1.0”},
    ​ @{@”title”:@”Book2”,@”price”:@”4.0”}
    ​ ]
    ​ };

综合上面两种情况:现在的JSPerson头文件如下:

​ @interface JSPerson : NSObject

@property (nonatomic ,assign)   NSInteger           age;
@property (nonatomic ,strong)   NSNumber           *height;
@property (nonatomic ,copy)     NSString            *name;
@property (nonatomic ,copy)     NSString            *bio;
@property (nonatomic ,assign)   BOOL                sex;        //0.male 1.female

@property (nonatomic ,strong)   JSDog               *dog;

@property (nonatomic ,strong)   NSArray             *books;     //里面是JSBook对象

@end

针对情况1:

我们看到在JSPerson类中,给出了属性声明:

@property (nonatomic ,strong)   JSDog               *dog;

所以,我们是可以获取到其类:JSDog,而且根据字典提供的内容,我们可以获取dog属性的值。这跟之前的情况类似。无须赘述。

而针对情况2:

我们就一筹莫展了,我们不知道属性books里面包含的是什么类型啊!

所以,我们需要用户提供我们一个关于books里面包含对象的信息。

为此,我们定义了一个协议用来提供字典数组<->模型数组的映射关系。

@protocol JSModel <NSObject>

@optional

+ (NSDictionary *)objectInArray;

@end

嵌套字典的解析

首先,嵌套字典,我们一般指的是自定义类,不是Foundation中的类,或者是基础类型。据此:

if (propertyInfo.isNumber){
    // 字符串->数字
}else if (propertyInfo.isNSClass){
    // Foundation类解析
}else{
    //自定义类解析
}

最简单的,假设只有一层嵌套,即外层字典与内层字典。类的最外层字典与内层字典解析原理一致,所以就是递归。

所以,需要将内层字典的值转换为模型,赋值给外层属性即可。

//自定义类
//取出内层字典值
NSDictionary *dic = (NSDictionary*)value;
if (dic) {
    //将内层字典转换为模型
    id model = [propertyInfo.cls modelWithDic:dic];
    //将内层转换的模型,赋值给外层属性
    [self setValue:model forKey:propertyInfo.name];
}

嵌套数组的解析

嵌套数组的情况,稍微复杂,但原理还是那么简单,取值,转换模型,赋值三步走。

JSPerson.m中,实现该映射:

+ (NSDictionary *)objectInArray
{
    return @{
             @"books":@"JSBook"
             };
}

这里表面的是属性books里的是JSBook类的数组。

取值:

id value = [dic valueForKey:propertyInfo.name];
NSArray *objArr = (NSArray *)value;

转换:

//将value转换为数组
NSMutableArray *tmpObjArr = [NSMutableArray array];
NSArray *objArr = (NSArray *)value;
for (NSDictionary * obj in objArr) {
    id model = [cls modelWithDic:obj];
    [tmpObjArr addObject:model];
}

此时,转换完成,赋值:

[self setValue:tmpObjArr forKey:propertyInfo.name];

So easy.

但是,需要注意的是,如何判断该属性是数组?即NSArray或者NSMutableArray

上篇,在类型解析中,我们提取出了isNSClass,指示是否是Foundation中类。NSArray显然是。

而且,获取到的typeEncoding也是T@"NSArray",&,N,V_books,所以JSClassPropertyInfo中的clsNSArray(即从typeEncoding提取出的类名)。根据以上这些,进入我们的代码执行区域:

if (propertyInfo.isNumber){
    // 字符串->数字

}else if (propertyInfo.isNSClass){
    if ( [propertyInfo.cls isSubclassOfClass:[NSArray class]] ) {
        //字典数组->模型数组
    }
}else{
    //自定义类
 }

至此,所有工作都已完毕。

解析的方法是这样子的。

- (instancetype)modelWithDic:(NSDictionary *)dic {

    NSArray *properties = [self.class properties];

    for (int i = 0; i < properties.count ; i++) {
        JSClassPropertyInfo *propertyInfo = properties[i];
        id value = [dic valueForKey:propertyInfo.name];
        if (!value) continue;

        if (propertyInfo.isNumber){
            // 字符串->数字
            if ([value isKindOfClass:[NSString class]])
//                value = [[[NSNumberFormatter alloc]init] numberFromString:value];
            [self setValue:value forKey:propertyInfo.name];

        }else if (propertyInfo.isNSClass){
            if ( [propertyInfo.cls isSubclassOfClass:[NSArray class]] ) {
                //对象数组-》模型数组
                //books->JSBook
                if ([self.class respondsToSelector:@selector(objectInArray)]) {

                    NSDictionary *tmpDic = [ (id<JSModel>) self.class objectInArray];
                    NSString *className = tmpDic[propertyInfo.name];
                    id cls = NSClassFromString(className);
                    //将value转换为数组
                    NSMutableArray *tmpObjArr = [NSMutableArray array];
                    NSArray *objArr = (NSArray *)value;
                    for (NSDictionary * obj in objArr) {
                        id model = [cls modelWithDic:obj];
                        [tmpObjArr addObject:model];
                    }
                    [self setValue:tmpObjArr forKey:propertyInfo.name];
                }
            }else{
                [self setValue:value forKey:propertyInfo.name];
            }

        }else{
            //自定义类
            //取出内层字典值
            NSDictionary *dic = (NSDictionary*)value;
            if (dic) {
                //将内层字典转换为模型
                id model = [propertyInfo.cls modelWithDic:dic];
                //将内层转换的模型,赋值给外层属性
                [self setValue:model forKey:propertyInfo.name];
            }
        }

    }
    return self;
}

Tips

关于isKindOfClassisMemberOfClassisSubclassOfClass的区别。

  • isKindOfClass

    Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class

  • isMemberOfClass

    Returns a Boolean value that indicates whether the receiver is an instance of a given class.
    YES if the receiver is an instance of aClass, otherwise NO.

  • isSubclassOfClass

Returns a Boolean value that indicates whether the receiving class is a subclass of, or identical to, a given class.
​ YES if the receiving class is a subclass of—or identical to—aClass, otherwise NO.

代码

git