iOS runtime (三)(runtime学习之YYModel源码分析)


  本文要写的是开源库YYKit其中一个组件YYModel,这个组件的用途就是提供JSON/Dictionary<==>Model这间相互的自动转换。对于它支持些个功能、性能如何、及它是如何提高它的性能可查看YYModel、及YYKit作者的文章本文章是不会讲这些的,那我这篇文章主要讲什么呢,实现的细节原理,所以贴代码会比较多,并且是以加注释方式,阅读文章同时也要阅读YYModel源码或者贴出来的源码才能理解好,还会根据JSON/Dictionary==>Model这条线,讲解一下代码的流程。写这个目的学习并记录,当然希望也能够帮助到同样想了解YYModel的人更好理解并读懂YYModel。如果对于runtime不熟悉,建议先补充一下runtime相关知识点,可参考我前面的文章runtime分析理解。本文面向的是想了解YYModel内部实现的读者。


  数据结构

  我们都知道,数据结构决定算法。先来了解一下YYModel的数据结构。里面文件不多就NSObject+YYModel.h、NSObject+YYModel.m、YYClassInfo.h、YYClassInfo.m。先说YYClassInfo.h和YYClassInfo.m中的类

YYClassInfo它的类定义是这样

这里省略了它的方法,只留下它的属性。YYClassInfo保存了一个类(类对象,而不是实例对象)的类变量cls、父类变量superCls、元类metaCls、是否为元类isMeta、类名称、父类的YYClassInfo指针 superClassInfo、所有成员变量信息ivarInfos、所有方法信息methodInfos、所有属性信息propertyInfos。cls、superCls、metaCls、isMeta都比较简单,ivarInfos、methodInfos在YYModel中其实是不会用到,所以这里我们重点关重superClassInfo、propertyInfos。让我们来看一下,它是怎么建立并存储一个类以及它父类一直来顶层的NSObject类的属性信息。建立的入口的类方法classInfoWithClass:

可以看到,这段逻辑先从缓存看能不能拿到cls的YYClassInfo,如果拿到,直接返回,拿不到就去创建并获取YYClassInfo的信息,取到后就缓存起来。再看创建获取YYClassInfo的方法initWithClass:

看注释,这段代码不难理解,接下来就是_update方法,就是在这个方法内获取类的ivarInfos、methodInfos、propertyInfos。但是就如前面所说,我们只需关注propertyInfos。里面保存的是YYClassPropertyInfo,它的结构如下:

每个YYClassPropertyInfo实例对象就代表了类的一个属性,只不过它保存了更多信息,保存的信息里面我们看到有YYEncodingType type,这个其实就是对

NSString *typeEncoding的一个转换,转换成作者自定义可以快带使用的枚举,具体意义见YYEncodingType。为什么要保存属性的setter和getter,在作者的文章中说到:Key-Value Coding 使用起来非常方便,但性能上要差于直接调用 Getter/Setter,所以如果能避免 KVC 而用 Getter/Setter 代替,性能会有较大提升。

此时看回YYClassInfo的_update方法,里面这段

这里就是遍历类的所有属性,以属性作为参数,传给YYClassPropertyInfo,在其内部获取YYClassPropertyInfo所需信息,里面细节就不再细说。总的来说,类的信息YYClassInfo都被缓存的起来,并前通过superClassInfo 指针,建立了一个关系链,使得通过一个类就能拿到它自己以它一直往上所有父类的YYClassInfo信息。

接下来是NSObject+YYModel.h、NSObject+YYModel.m。其中y主要是_YYModelMeta、_YYModelPropertyMeta以下为它们的定义:

_YYModelPropertyMeta是跟_YYClassPropertyInfo一一对应的,只不过它多了_mappedToKey,_mappedToKeyPath,_mappedToKeyArray,和其它一些成员。这里举个列子就明白了

这个Book,它就会有四个_YYClassPropertyInfo。前两个情况是一样的,_mappedToKey值为 "n"、"p",第三个_mappToKeyPath值为"ext.desc",第四个_mappedToKeyArray值是@[@"id",@"ID",@"book_id"]。所以如果有以下json

// JSON:
{
    "n":"Harry Pottery",
    "p": 256,
    "ext" : {
        "desc" : "A book written by J.K.Rowing."
    },
    "ID" : 100010
}

YYModel就能根据_YYClassPropertyInfo中_mappedToKey,_mappedToKeyPath,_mappedToKeyArray拿到json里面的值,并设置到Book的相应属性中。

接下来,就是_YYModelMeta。它里面有四个成员,它们都是容器,里面都是保存_YYClassPropertyInfo,但是还是有所区别

_allPropertyMetas:这个保存了所有从YYClassInfo在其继承关系中的所有属性propertyInfo转换得来的_YYClassPropertyInfo,只不过它是最原始的,还没有与json中的key做任何关联,即_YYClassPropertyInfo中的_mappedToKey,_mappedToKeyPath,_mappedToKeyArray都还是为nil。

_mapper:这个是在_allPropertyMetas基础上已经建立好与json的关系的,即_mappedToKey,_mappedToKeyPath,_mappedToKeyArray至少有一个不为空的。

_keyPathPropertyMetas:这个只保存_mappedToKeyPath不为空的所有_YYClassPropertyInfo。

_multiKeysPropertyMetas:这个只保存_mappedToKeyArray不为空的所有_YYClassPropertyInfo。

另外还有容器类属性、及黑名单与白名单逻辑,这两个比较简单,不展开。下面是它建立映射关系的实现代码:

- (instancetype)initWithClass:(Class)cls {
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];
    
    // Get black list
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }
    
    // Get white list
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }
    
    // Get container property's generic class
    //获取容器内对应的类。
    NSDictionary *genericMapper = nil;
    if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
        genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
        if (genericMapper) {
            NSMutableDictionary *tmp = [NSMutableDictionary new];
            [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
                if (![key isKindOfClass:[NSString class]]) return;
                Class meta = object_getClass(obj);
                if (!meta) return;
                if (class_isMetaClass(meta)) {
                    tmp[key] = obj;
                } else if ([obj isKindOfClass:[NSString class]]) {
                    Class cls = NSClassFromString(obj);
                    if (cls) {
                        tmp[key] = cls;
                    }
                }
            }];
            genericMapper = tmp;
        }
    }
    
    //创建所有属性的metas,以名字作为key
    // Create all property metas.
    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;
            _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;
        }
        curClassInfo = curClassInfo.superClassInfo;
    }
    if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
    
    // create mapper
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    
    //当自定义属性对应关系才会走这,也就是应用层属性名与返回json的key是不一样时,根据modelCustomPropertyMapper上注释的例子写的算法,生成对应的数据结构
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            [allPropertyMetas removeObjectForKey:propertyName];
            
            //NSString 两种情况,1、简单的key对应@"name"  : @"n", 2、keyPath方法:@"desc"  : @"ext.desc"
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) return;
                
                propertyMeta->_mappedToKey = mappedToKey;
                NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
                for (NSString *onePath in keyPath) {
                    if (onePath.length == 0) {
                        NSMutableArray *tmp = keyPath.mutableCopy;
                        [tmp removeObject:@""];
                        keyPath = tmp;
                        break;
                    }
                }
                if (keyPath.count > 1) {
                    propertyMeta->_mappedToKeyPath = keyPath;
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil; //检查是否有同样key对应多个属性,这里有个小技巧,当mapper[mappedToKey]指向了当前最新那个propertyMeta,前一个mapper[mappedToKey]被记录到了当前propertyMeta->_next里面了,所以要读取到所到相同mappedToKey的propertyMeta时,只要mapper[mappedToKey],mapper[mappedToKey]->_next,不停遍历,直到nil即可
                mapper[mappedToKey] = propertyMeta;
                
            }
            //一个属性,对应多个不同json里的key时,如:@"bookID": @[@"id", @"ID", @"book_id"]
            else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                
                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->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    
    //在allPropertyMetas剩余下来的只要简单做一遍关联即可,因为什么有自定义关联
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    if (mapper.count) _mapper = mapper;
    if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas; //keypath 类型的
    if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; //对应多个json key类型的。
    
    _classInfo = classInfo;
    _keyMappedCount = _allPropertyMetas.count;
    _nsType = YYClassGetNSType(cls);
    _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);
    _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
    _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
    _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);
    
    return self;
}

到此,主要的数据结构已经介绍完成了。


JSON/Dictionary==>Model

下面就是json自动转换成Model的关键入口,已经带注释

- (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)];
    if (modelMeta->_keyMappedCount == 0) return NO;
    
    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);
    
    /*使用CFDictionaryApplyFunction方式提高迭代遍历的性能*/
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        //如果model的数据量多于json数据量,迭代遍历json的进行赋值,减少不必要的迭代
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        
        //上面只会迭代简单的key映射关系的,所以这里分别还有做对于keyPath及,keyArray关联的迭代
        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 {
        //如果model的数据量少于json数据量, 迭代遍历model的数据进行赋值,
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    //如果有自定义转换方法,前面所做的都会丢弃
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

接着就会来到函数ModelSetWithPropertyMetaArrayFunction中,下面继续见代码

static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
    //拿到propertyMeta,然后分别根据_mappedToKeyArray或_mappedToKeyPath或_mappedToKey在json dictionary中去拿到值
    if (!propertyMeta->_setter) return;
    id value = nil;
    
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        
        //拿到值后就往的属性里设置
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

看注释就能明白了,那么最后就到了这个设置值到属性的函数ModelSetValueForProperty,这个函数相当长,但是原理都是差不多的,所以下面只会讲解两种情况,剩下的有兴趣自己可以继续研究。

1、当要设置的属性是个C语言的基础数据类型,其实就是从_YYModelPropertyMeta中拿到setter,再根据YYEncodingType取NSNumber中的值然后设置进去,下面就省略掉一些case。

static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
                                                  __unsafe_unretained NSNumber *num,
                                                  __unsafe_unretained _YYModelPropertyMeta *meta) {
    switch (meta->_type & YYEncodingTypeMask) {
        case YYEncodingTypeBool: {
            ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
        } break;
  ... ...
case YYEncodingTypeLongDouble: { long double d = num.doubleValue; if (isnan(d) || isinf(d)) d = 0; ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d); } // break; commented for code coverage in next line default: break; } }

2、如果要设置的属性为自定义类类型,请看以下代码,只取片段

        switch (meta->_type & YYEncodingTypeMask) {
            //如果property是个自定义对类,即继承自NSObject
            case YYEncodingTypeObject: {
                //如果拿到对应json为kCFNull,property被设置为nil
                if (isNull) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
                } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
                    //如果从json中拿到的值,已经是property的cls,直接设置
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
                } else if ([value isKindOfClass:[NSDictionary class]]) {
                    //如果从json中拿到的值是个字典
                    NSObject *one = nil;
                    if (meta->_getter) {
                        //从getter中能拿到实例
                        one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
                    }
                    if (one) {
                        //拿到了就递归调用yy_modelSetWithDictionary
                        [one yy_modelSetWithDictionary:value];
                    } else {
                        //否则,先查看用户是否对此类有自定义的类联,目的是解决这个是个基类指针,要根据value里面的值创建不同有子类,详情看modelCustomClassForDictionary声明
                        Class cls = meta->_cls;
                        if (meta->_hasCustomClassFromDictionary) {
                            cls = [cls modelCustomClassForDictionary:value];
                            if (!cls) cls = meta->_genericCls; // for xcode code coverage
                        }
                        one = [cls new];
                        [one yy_modelSetWithDictionary:value];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                    }
                }
            } break;
       ......
    }

就是根据这样的思路,无论是什么类型,有的需要递归,有的不需递归。这样就实现了自动json值被设置到了model中去了。这里省了很多情况,如属性是容器类型是如何自定义关联里面元素,是结构体等等。还有JSON/Dictionary==>Model的流程又是怎么样。这里就不继续往下了,因为实在又长又臭了已经。相信如果前面说的内容都能理解了,有了这样的基础,要理解全部细节并不会很困难。

优质内容筛选与推荐>>
1、总结篇——从零搭建maven多模块springboot+mybatis项目
2、golang函数调用计时
3、机器学习和深度学习资料列表
4、react 中的 setState
5、软件工程课堂作业(八)——结对开发(三)


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn