zoukankan      html  css  js  c++  java
  • 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的流程又是怎么样。这里就不继续往下了,因为实在又长又臭了已经。相信如果前面说的内容都能理解了,有了这样的基础,要理解全部细节并不会很困难。

    作者:xianmingchen
    出处:http://www.cnblogs.com/chenxianming/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任权利。
  • 相关阅读:
    图像,script,link 空地址带来的困惑
    localStorage变更事件当前页响应新解-awen
    opencv在ios上的开发教程
    torch-ios框架XCODE使用备忘
    重拾老本行_图像处理
    dialogic d300语音卡驱动重装后启动报错问题解决方法
    Winpcap构建用户级网桥
    winpcap usb山寨网卡识别
    最近这段时间
    银行IT入门深似海
  • 原文地址:https://www.cnblogs.com/chenxianming/p/5640869.html
Copyright © 2011-2022 走看看