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的流程又是怎么樣。這里就不繼續往下了,因為實在又長又臭了已經。相信如果前面說的內容都能理解了,有了這樣的基礎,要理解全部細節並不會很困難。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM