YYModel源码阅读(二)

1,659 阅读6分钟

本篇文章是YYModel源码阅读的第二篇文章, 主要是对NSObject+YYModel中源码阅读做的笔记.

YYModel主要提供json转model的接口, 内部是利用YYClassInfo封装好的接口实现对json数据的解析.

分析方面:

  1. YYModel属性和接口信息
  2. YYModelPropertyMeta
  3. YYModelMeta
  4. 模型转换
  5. 格式转换 类型编码
  6. 性能优化点

YYModel属性接口信息

NSObject+YYModel文件中提供了NSObject``NSArray``NSDictionary的分类, 提供了json转模型的方法, 无侵入性, 在YYModel中提供了YYModel协议, 进行自定义操作.

YYModel协议:

// 当json中的key和属性的propertyName不一致的时候, 需要使用这个方法, 进行匹配操作
/*
    + (NSDictionary *)modelCustomPropertyMapper {
        return @{@"name"  : @"n",
                 @"page"  : @"p",
                 @"desc"  : @"ext.desc", // keyPath
                 @"bookID": @[@"id", @"ID", @"book_id"]}; // mutiKeys
    }
*/
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;

// 用于设置集合内部元素类型, 例如设置数组中model的class
/*
    @property NSArray *shadows;
    @property NSSet *borders;
    @property NSDictionary *attachments;
    --------------------------
    + (NSDictionary *)modelContainerPropertyGenericClass {
        return @{@"shadows" : [YYShadow class],
                 @"borders" : YYBorder.class,
                 @"attachments" : @"YYAttachment" };
    }
*/
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;

// 根据json中的字段不同, 可以将json解析成不同的class
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;

// 黑名单: 黑名单中的属性将不会被解析, nil表示忽略
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;

// 白名单: 如果属性不在白名单中, 属性将不会被解析, nil表示忽略
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;

// 在json转换之前调用, 可以对json进行修改
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
...

YYModelMeta

YYModelMeta主要存储类型相关信息, 内部是对YYClassInfo的封装, 在YYModelMeta内部记录着所有的属性(YYModelPropertyMeta), keyPath对应的YYModelPropertyMeta, 以及multiKeys对应的YYModelPropertyMeta, NSType类类型等信息.

/// 模型类信息
@interface _YYModelMeta : NSObject {
    @package
    // runtime类信息
    YYClassInfo *_classInfo;
    /// Key:mapped key and key path, Value:_YYModelPropertyMeta.
    NSDictionary *_mapper;
    /// Array<_YYModelPropertyMeta>, all property meta of this model.
    /// array<YYModelPropertyMeta> 当前模型的所有propertyMeta数组
    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.
    /// 被映射到多个key的_YYModelPropertyMeta数组
    NSArray *_multiKeysPropertyMetas;
    /// The number of mapped key (and key path), same to _mapper.count.
    /// 映射key的数量, =_mapper.count
    NSUInteger _keyMappedCount;
    /// Model class type. 模型cls类型
    YYEncodingNSType _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}
@end

在YYModelMeta的类构造方法中, 使用static CFMutableDictionaryRef cache以类型cls作为key将元类对象做了全局内存缓存, 避免在下次使用时重复创建(类的元数据中存放的是类的相关信息, 不会改变)

+ (instancetype)metaWithClass:(Class)cls {
    // 类元数据的缓存
    static CFMutableDictionaryRef cache;
    
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    
    // 从缓存中获取元类
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    
    if (!meta || meta->_classInfo.needUpdate) {
        // 创建元类
        meta = [[_YYModelMeta alloc] initWithClass:cls];
        if (meta) {
            // 缓存
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);
        }
    }
    return meta;
}

YYModelPropertyMeta

@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< property's name, 属性名称
    YYEncodingType _type;        ///< property's type, 属性类型, OC类型解析为YYEncodingTypeObject
    YYEncodingNSType _nsType;    ///< property's Foundation type, 属性在Foundation框架中的类型
    BOOL _isCNumber;             ///< is c number type, 是否是CNumber
    Class _cls;                  ///< property's class, or nil, 属性类
    Class _genericCls;           ///< container's generic class, or nil if threr's no generic class,如果是容器类型, 就是容器内元素的类型(NSArray中存放的item类型)
    SEL _getter;                 ///< getter, or nil if the instances cannot respond, getter方法
    SEL _setter;                 ///< setter, or nil if the instances cannot respond, setter方法
    BOOL _isKVCCompatible;       ///< YES if it can access with key-value coding, 是否支持KVC
    BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver, 结构体是否支持归档/解档
    BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:, 是否实现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, 映射key json中的key
    NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path), 映射keyPath
    NSArray *_mappedToKeyArray;  ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys), 映射的key/keyPath数组, 如果没有就为nil
    YYClassPropertyInfo *_info;  ///< property's info, 属性信息
    _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key. 如果多个属性映射到同一个key, next就指向下一个模型属性元
}
@end

在YYModelPropertyMeta中已经包含了YYClassPropertyInfo信息, YYClassPropertyInfo中已经包含了getter和setter方法, 为什么还要在YYModelPropertyMeta中提供成员变量_getter_setter呢?

查看源码在为model的属性赋值的时候, 使用的是objc_msgSend, 作者在iOS JSON 模型转换库评测](blog.ibireme.com/2015/10/23/…)中提到使用getter和setter方法替代KVC在性能上会有很大提升, 直接操作成员变量相比getter和setter方法又有较大的提升.

json to model

json转model主要是使用yy_modelSetWithDictionary:(NSDictionary *)di:方法

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    // 容错处理 kCFNull NSNull
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    
    // 根据自身生成YYModelMeta模型属性, 所有key的处理已经在YYModelMeta的初始化方法中处理完成
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    if (modelMeta->_keyMappedCount == 0) return NO;
    
    // 转换之前对json进行自定义处理
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    
    /*
     typedef struct {
         void *modelMeta;  ///< _YYModelMeta
         void *model;      ///< id (self)
         void *dictionary; ///< NSDictionary (json)
     } ModelSetContext;
     */
    // 设置模型上下文
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
    
    // 如果modelMeta个数大于json中的key个数 为什么做这个判断呢?
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        // 字典转模型
        // typedef void (*CFDictionaryApplierFunction)(const void *key, const void *value, void *context);
        // 遍历字典, 并且每遍历一次执行一次函数
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        // 属性元对应字典中的多个key
        // 自定义的key值无效?
        // typedef void (*CFArrayApplierFunction)(const void *value, void *context);
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    // 上下文
    ModelSetContext *context = _context;
    // 类元数据
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    // 从类元数据的mapper中通过key取出对应的propertyMeta
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    // model
    __unsafe_unretained id model = (__bridge id)(context->model);
    // 根据value类型
    // model set propertyMeta.setter value
    while (propertyMeta) {
        // 调用setter方法 赋值
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

ModelSetWithDictionaryFunction中是通过key-value键值对, 获取元类YYModelMeta对应key的propertyMeta, 将value数据通过setter方法赋值给属性, ModelSetValueForProperty方法中主要对value类型做了处理, 根据属性类型将value转换为属性对应类型.

YYModel中提升性能的优化

  1. 将类的元数据在内存中做缓存, YYModelMeta中使用单例将类的元数据存放到单例中.
  2. 在为属性赋值的时候, 效率: 成员变量 > getter setter > objc_msgSend > KVC
  3. 遍历字典和数组时 使用C函数, 提高效率, CFDictionaryApplyFunction和CFArrayApplyFunction
  4. 使用存C函数和内联函数代替OC方法, OC方法调用会带来消息发送的开销, 如果C函数较小, 使用内联函数可以避免压栈出栈的操作.
  5. ARC内存管理, 使用__unsafe_unretained
  6. 在转换之前Model的属性个数和JSON中键的个数是确定的, 选择循环较少的方式遍历. 源码中将modelMeta->_keyMappedCount和dic的键个数做对比, 当属性个数较多是, 选择dic进行遍历. 当属性个数较少时, 遍历属性.

思考

  1. hash
  2. 什么类型不能使用KVC?
  3. NSType哪些类型
  4. CNumber类型
  5. 类型之间的转换 string->date
  6. 获取NSBlock类型的技巧
  7. 遍历字典和数组的性能 方法

参考文章

[iOS JSON 模型转换库评测](blog.ibireme.com/2015/10/23/…)