YYModel源码解析(三)NSObject+YYModel

2017-07-10  本文已影响208人  胡小夜大叔

1.YYModel

首先YYModel是以类别的方式对NSObject、NSArray、NSDictionary进行的扩展,这样的直接好处就是对你自己的工程代码入侵性小,非常好接入,并且有一个YYModel的协议用来满足你自己的定制需求。

2._YYModelMeta 和 _YYModelPropertyMeta

先看两个很重要的类

_YYModelMeta:

   /// A class info in object model.
   @interface _YYModelMeta : NSObject {
       @package
       YYClassInfo *_classInfo;
       /// Key:mapped key and key path, Value:_YYModelPropertyMeta.
       NSDictionary *_mapper;
       /// Array<_YYModelPropertyMeta>, all property meta of this model.
       NSArray *_allPropertyMetas;
       /// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
       NSArray *_keyPathPropertyMetas;
       /// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
       NSArray *_multiKeysPropertyMetas;
       /// The number of mapped key (and key path), same to _mapper.count.
       NSUInteger _keyMappedCount;
       /// Model class type.
       YYEncodingNSType _nsType;

       BOOL _hasCustomWillTransformFromDictionary;
       BOOL _hasCustomTransformFromDictionary;
       BOOL _hasCustomTransformToDictionary;
       BOOL _hasCustomClassFromDictionary;
   }
   @end

解释下这些属性:

再看下_YYModelMeta的些实现:

_YYModelPropertyMeta:

/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< property's name
    YYEncodingType _type;        ///< property's type
    YYEncodingNSType _nsType;    ///< property's Foundation type
    BOOL _isCNumber;             ///< is c number type
    Class _cls;                  ///< property's class, or nil
    Class _genericCls;           ///< container's generic class, or nil if threr's no generic class
    SEL _getter;                 ///< getter, or nil if the instances cannot respond
    SEL _setter;                 ///< setter, or nil if the instances cannot respond
    BOOL _isKVCCompatible;       ///< YES if it can access with key-value coding
    BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
    BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:

    /*
     property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;      ///< the key mapped to
    NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path)
    NSArray *_mappedToKeyArray;  ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
    YYClassPropertyInfo *_info;  ///< property's info
    _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
}
@end

其实大部分属性看一眼名字或作者的注释就知道什么意思什么用了,就不做解释了,重点解释几个一眼看不出来具体什么作用的解释:

3.几个私有方法

用meta来给model赋值,这里比用字典来赋值多了一点东西就是,他会根据meta的映射类型,去取出对应的value再去赋值。

4.一般的YYModel工作流程

我们用的最多的就是 yy_modelWithJSON :
+ (instancetype)yy_modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    return [self yy_modelWithDictionary:dic];
}

_yy_dictionaryWithJSON 主要是把传进来的json参数转化为字典类型,然后供后边使用

然后看 yy_modelWithDictionary :
+ (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];
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }

    NSObject *one = [cls new];
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

这里在拿到_YYModelMeta之后,判断了下modelMeta->_hasCustomClassFromDictionary,这个是看是否需要在json到model对象转换中创建不同类的实例。

然后最主要看转换过程 yy_modelSetWithDictionary,具体看注释:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;

    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];  //获取到对应的modelMeta,这里有完整的映射关系,和属性meta描述
    if (modelMeta->_keyMappedCount == 0) return NO;  //如果map里没有映射关系,返回NO

    //模型转换的前处理,看使用者是否需要在模型转换前对dic做一些啥特殊处理,返回处理后的dic
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }

    //对应到结构体里
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);

    //转换流程
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        //如果model的属性数量,大于等于json源字段的数量,说明model中可能存在多个属性对应一个json源字段的或者有无法和json源字段对应的

        //让dic里的元素全部执行一遍ModelSetWithDictionaryFunction,即赋值流程
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);

        //如果model里有路径映射的,则都执行一遍ModelSetWithPropertyMetaArrayFunction,对路径映射的属性赋值
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                             CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
        }

        //如果model里有多属性映射的的,则都执行一遍ModelSetWithPropertyMetaArrayFunction
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                             CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
        }
    } else {
        //如果model的属性数量小于json源字段的数量,那只需要找到model属性对应的json字段就可以,则用ModelSetWithPropertyMetaArrayFunction转换
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                         CFRangeMake(0, modelMeta->_keyMappedCount),
                         ModelSetWithPropertyMetaArrayFunction,
                         &context);
    }

    //转换后处理,看使用者是否需要些额外处理
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

这就完成了转换,完美~~~

上一篇 下一篇

猜你喜欢

热点阅读