YYModel 学习(二)

作者: 天空中的球 | 来源:发表于2016-07-01 16:01 被阅读1584次

    接着上篇的学习,本篇以以下两个地方为着手点继续学习。

    // JSON 如何转化为Model
    + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary
    // YYModel 代理
    @protocol YYModel <NSObject>
    

    一、 JSON 转化为Model

    通常我们在用YYModel时,需要将JSON 转为Model的情况下,通常会涉及到以下三个方法:

    @interface NSObject (YYModel)
    + (nullable instancetype)yy_modelWithJSON:(id)son;
    @end
    
    @interface NSArray (YYModel)
    + (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json;
    @end
    
    @interface NSDictionary (YYModel)
    + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
    @end
    

    但是以上三个方法最终都会要调用一下的方法:

    + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
        if (!dictionary || dictionary == (id)kCFNull) return nil;
        if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
        
        Class cls = [self class];
        _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
        // 是否已经使用自定义NSDictionary 转化过来
        if (modelMeta->_hasCustomClassFromDictionary) {
            cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
        }
        // 刚好在这个地方确定 class 类型
        NSObject *one = [cls new];
        // 判断是否已经按专门方式处理过了,**核心重点处**
        if ([one yy_modelSetWithDictionary:dictionary]) return one;
        return nil;
    }
    
    - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
        if (!dic || dic == (id)kCFNull) return NO;
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
        
        // 注意此处用的是object_getClass(self)
        _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
        if (modelMeta->_keyMappedCount == 0) return NO;
        // 将会从字典转过来
        if (modelMeta->_hasCustomWillTransformFromDictionary) {
            dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
            if (![dic isKindOfClass:[NSDictionary class]]) return NO;
        }
        
        /** 三种类型
         void *modelMeta;  ///< _YYModelMeta
         void *model;      ///< id (self)
         void *dictionary; ///< NSDictionary (json)
         */
        ModelSetContext context = {0};
        context.modelMeta = (__bridge void *)(modelMeta);
        context.model = (__bridge void *)(self);
        context.dictionary = (__bridge void *)(dic);
        
        /**
         * 先了解下 CFArrayApplyFunction CFDictionaryApplyFunction
            目的:遍历Array数组元素,每一次传入一个函数中进行处理
            优点:遍历容器类能带来性能提升
            函数处理方式(CFArrayApplierFunction):ModelSetWithPropertyMetaArrayFunction
         */
        
        if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
            
            /**先对字典中的Value 进行处理,通常情况下一般都通过
               ===> ModelSetWithDictionaryFunction
               ===> ModelSetValueForProperty
               ===>  ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
              然后可能就直接设置完了。
              */
            CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
            /**下面是为了适配其他情况做的处理*、
             //对应类型 @{@"name":@"user.name"}
            if (modelMeta->_keyPathPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
            // 对应类型 @{@"name":@[@"name",@"oldname",@"newname"]}
            if (modelMeta->_multiKeysPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
        } else {
            // 对象类型 @{@"name":@"name"}
            CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                                 CFRangeMake(0, modelMeta->_keyMappedCount),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        // 有没有从NSDictionary 转过成有效的Model
        if (modelMeta->_hasCustomTransformFromDictionary) {
            return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
        }
        return YES;
    }
    
    

    先让要转化的 object 统一都转化成我们需要的 NSDictionary(这一步上面我没有贴出来),然后再转化为我们需要的 Model Object,当然里面涉及到诸多判断和转化,最核心还是对YYModelMeta的使用,然后再对其进行处理专门的ModelSetWithPropertyMetaArrayFunction函数处理。

    PS:里面YYModelMeta对象里面的属性是通过YYModel 代理的设置来的

    static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
        /**一个结构体,包含 _YYModelMeta ,id (self),NSDictionary (json) */
        ModelSetContext *context = _context;
        // 分别获取结构体中部分
        __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
        __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
        if (!propertyMeta->_setter) return;
        id value = nil;
        // 判断三种基本的映射关系
        // 并且转化成字典中 value (YYValueForKeyPath这个方法的获取)
        if (propertyMeta->_mappedToKeyArray) {
            value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
        } else if (propertyMeta->_mappedToKeyPath) {
            value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
        } else {
            value = [dictionary objectForKey:propertyMeta->_mappedToKey];
        }
        
        // 如果取到了有效的值,那就转化为我们需要的 Model
        if (value) {
            __unsafe_unretained id model = (__bridge id)(context->model);
            // 核心
            ModelSetValueForProperty(model, value, propertyMeta);
        }
    }
    

    此时还还是仅仅去处有效的 value,还没有正式转化为 Model,这时ModelSetValueForProperty

    /**  Set value to model with a property meta */
    static void ModelSetValueForProperty(__unsafe_unretained id model,
                                         __unsafe_unretained id value,
                                         __unsafe_unretained _YYModelPropertyMeta *meta)
    

    里面根据 meta的类型又进行了诸多判断,重点可以看

    case YYEncodingTypeNSString:
    case YYEncodingTypeNSArray:
    case YYEncodingTypeNSDictionary:
    

    当然此处是和可变的情况一起处理,就以字符串举例

    case YYEncodingTypeNSString:
    case YYEncodingTypeNSMutableString: {
        if ([value isKindOfClass:[NSString class]]) {
            // 普通字符串
            if (meta->_nsType == YYEncodingTypeNSString) {
                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
            } else {
                 // 可变字符串
                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
            }
        } else if ([value isKindOfClass:[NSNumber class]]) {
             
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                           meta->_setter,
                                                           (meta->_nsType == YYEncodingTypeNSString) ?
                                                           ((NSNumber *)value).stringValue :
                                                           ((NSNumber *)value).stringValue.mutableCopy);
        } else if ([value isKindOfClass:[NSData class]]) {
            NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
        } else if ([value isKindOfClass:[NSURL class]]) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                           meta->_setter,
                                                           (meta->_nsType == YYEncodingTypeNSString) ?
                                                           ((NSURL *)value).absoluteString :
                                                           ((NSURL *)value).absoluteString.mutableCopy);
        } else if ([value isKindOfClass:[NSAttributedString class]]) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                           meta->_setter,
                                                           (meta->_nsType == YYEncodingTypeNSString) ?
                                                           ((NSAttributedString *)value).string :
                                                           ((NSAttributedString *)value).string.mutableCopy);
        }
    } break;
    
    

    此处也充分体现了严谨之处啊,可变和不可变,还有就是nsType都处于处于同一种类型下,还分分别给像NSNumberNSURLNSAttributedString的情况处理。简单的说,明明你标明了NSString,却给你传了一个其他类型的,这边同样都做了处理。

    经过上述转化后,然后再调用下 modelsetter方法转化成我们需要的啦。

    /**
        model: 谁执行
        meta->_setter: 执行的方法
        value: 传的值
    */
     ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
    

    等着所有的属性都走一遍之后,到此基本的 JSON 转化为 Model 就结束啦。

    二、 YYModel中的代理

    @protocol, 其实也是我们Model中写的最多一块

    //返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
    + (NSDictionary *)modelCustomPropertyMapper;
    
    // 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。
    + (NSDictionary *)modelContainerPropertyGenericClass;
    
    // 如果实现了该方法,则处理过程中会忽略该列表内的所有属性
    + (NSArray *)modelPropertyBlacklist ;
    
    // 如果实现了该方法,则处理过程中不会处理该列表外的属性。
    + (NSArray *)modelPropertyWhitelist ;
    

    进行进一步探索,发现,原来@protocol中的实现都是在YYModelMeta中的实现的。

    @implementation _YYModelMeta
    - (instancetype)initWithClass:(Class)cls {
        YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
        if (!classInfo) return nil;
        self = [super init];
    
        // TO DO 处理,也是处理代理的地方
    
        return self;
    }
    

    重点看一下对modelCustomPropertyMapper的处理

    // 用字典添加 所有属性的PropertyMeta对象
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;
    while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
        for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
            if (!propertyInfo.name) continue;
            // 黑名单处理
            if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
            // 白名单处理
            if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
            // 创建一个我们需要的meta对象
            _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
                                                                    propertyInfo:propertyInfo
                                                                         generic:genericMapper[propertyInfo.name]];
            if (!meta || !meta->_name) continue;
            if (!meta->_getter || !meta->_setter) continue;
            // 避免重复操作
            if (allPropertyMetas[meta->_name]) continue;
            allPropertyMetas[meta->_name] = meta;
        }
        // 遍历父类的Property [while 语句中下一个]
        curClassInfo = curClassInfo.superClassInfo;
    }
    // 判断是否为空之后的处理
    if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
    
    // 对三种不同的映射关系进行处理
    /**
       NSString *_mappedToKey =====  映射类型 @{@"name":@"name"}
       NSArray *_mappedToKeyPath ==== 映射类型 @{@"name":@"user.name"}
       NSArray *_mappedToKeyArray ===== 映射类型 @[@"name",@"oldname",@"newname"]
     */
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            // 拿 上面获取到的 propertyMeta 对象
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            // 删除之前映射规则,因为我们待会有新的规则
            [allPropertyMetas removeObjectForKey:propertyName];
            
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) return;
                
                propertyMeta->_mappedToKey = mappedToKey;
                NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
                // 此时表明 新的映射 是 mappedToKeyPath 规则
                if (keyPath.count > 1) {
                    propertyMeta->_mappedToKeyPath = keyPath;
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                // 此时就是相当于 赋予 新的 规则,新的映射关系
                mapper[mappedToKey] = propertyMeta;
                
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                // 这个地方对应的就是 mappedToKeyArray
                NSMutableArray *mappedToKeyArray = [NSMutableArray new];
                for (NSString *oneKey in ((NSArray *)mappedToKey)) {
                    if (![oneKey isKindOfClass:[NSString class]]) continue;
                    if (oneKey.length == 0) continue;
                    // 同样的判断 是哪一种映射关系
                    NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
                    if (keyPath.count > 1) {
                        [mappedToKeyArray addObject:keyPath];
                    } else {
                        [mappedToKeyArray addObject:oneKey];
                    }
                    // 给予新的映射关系
                    if (!propertyMeta->_mappedToKey) {
                        propertyMeta->_mappedToKey = oneKey;
                        propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
                    }
                }
                if (!propertyMeta->_mappedToKey) return;
               // 给予新的映射规则后并将这个propertyMeta放到相应的数组中
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                // 在多个属性映射一个 key 的时候使用
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    
    /** 这个地方就是对上面 modelCustomPropertyMapper 中的查漏补缺,
        处理些没有自定义的属性,让它们属性的mappedKey等于属性名
    */
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        // 在多个属性映射一个 key 的时候使用
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    

    到这里,我们一个我们需要的 propertyMeta就已经产生了,然后就可以配合着上面的 字典转 Model ,基本的一套流程就可以走啦。

    了解到此,对里面的实现已经有了个粗略的思路,从上面的实现发现其中的映射关系是必须先要了解的,那就是属性名和json key的对应

    映射表
    @{ @"name" : @"name"} //_mappedTokey
    
    @{ @"name" : [@"name",@"oldname",@"newname"]} //_mappedToKeyArray
    
    @{ @"name" : @"use.name"} //_mappedToKeyPath
    
    @{ 
          @"name" : @"name",
          @"fullName" : @"name", 
          @"username" : @"name"
    } // _next
    

    特别要注意的是_next,这种对第四种情况的处理。

    感觉目前自己对YYModel中还是一个混乱的认识,毕竟 runtime 那块东东还没有去分析,宏观上也缺乏一个很好的认识,所以下一篇学习是必须的啦。

    备注参考:
    http://www.jianshu.com/p/9723761d02db

    相关文章

      网友评论

      • 曾经像素有点低:NSArray* array = [NSArray modelArrayWithClass:[Model class] json:responseObject[@"data"]];

        使用这个方法的时候。model里边的- (void)setValue:(id)value forUndefinedKey:(NSString *)key 方法不走。怎么才能解决。比如我的后台字段中有一个id

      本文标题:YYModel 学习(二)

      本文链接:https://www.haomeiwen.com/subject/kgbfjttx.html