YYModel V1.0.4源碼解析

YYKit出現了很長時間了,一直想要詳細解析一下它的源碼,都是各類原因推遲了。
最近稍微閒了一點,決定先從最簡單的YYModel開始吧。
首先,我也先去搜索了一下YYModel相關的文章,解析主要API和用法的居多,也有不少人大呼看不懂。其實主要仍是要靜下心來看,由於YY用了不少並不常見,也不經常使用的底層API,你一個個去查官方文檔會發現,他們並不難。json

YYModel 裏對轉換過的model 屬性作了緩存、對ClassInfo、MethodInfo、PropertyInfo等作了一層封裝,這些也是爲了便於緩存和取值。數組

之前我在寫runtime 小結的時候,就說過全部解析json 或者自動實現其餘數據轉換爲model的,最終都是利用runtime 來動態獲取model的屬性、示例變量等。YYModel也是利用這個來實現的,摘自YYClassInfo中的_update方法:緩存

Class cls = self.cls;
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(cls, &methodCount);
    if (methods) {
        NSMutableDictionary *methodInfos = [NSMutableDictionary new];
        _methodInfos = methodInfos;
        for (unsigned int i = 0; i < methodCount; i++) {
            YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
            if (info.name) methodInfos[info.name] = info;
        }
        free(methods);
    }
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
    if (properties) {
        NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i < propertyCount; i++) {
            YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (info.name) propertyInfos[info.name] = info;
        }
        free(properties);
    }

    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars) {
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }

YYModel 調用順序一覽

上面先提到了json 轉 Model的精髓,而後下面就要一步一步的瞭解,YYModel 是如何實現將json 轉爲Model的。安全

第一步

關於第一步,要提到以下兩個方法:網絡

/** 這個方法是將json 轉換爲model(使用機率低)
這個方法內部其實也分爲兩步:
第一步,將json 轉換爲 dict;
第二步,調用下面那個方法將dict 轉換爲 model
*/
+ (nullable instancetype)yy_modelWithJSON:(id)json;

// 這個方法是將dict 轉換爲model(使用機率高)
+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;

上面這個兩個方法,第二個方法應該是使用的最多的,第一個方法感受極少有機會能用到吧。
爲何這麼說呢?
由於咱們的網絡接口每每都會包含成功失敗的bool值、狀態碼、message、以及數據(多是數組、字典、字符串等),咱們須要先將接口返回的json結構轉爲字典後,判斷bool值或狀態碼,來肯定是否要進一步解析數據。所以第二個方法應該是使用概率最大的方法。併發

第二步

第二步,就要開始解析上面的方法二了。先來看看源碼,我加了一些中文註釋:app

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    // 首先校驗參數
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;

    // 這裏的cls 就是咱們的實際model 類型
    Class cls = [self class];
    // 這一步,是重中之重,經過class 獲取到各類信息,而後封裝到_YYModelMeta中。
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }

    NSObject *one = [cls new];
    // 這裏也是幾位關鍵的一步,轉換model 並賦值
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

那麼_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];中作了些什麼呢?
在解惑上面問題前,咱們先來看看_YYModelMeta中都有些什麼?函數

@interface _YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo;
    /// 字典,鍵是屬性名,值是_YYModelPropertyMeta類型的對象
    NSDictionary *_mapper;
    /// 這個Model 對象的屬性(屬性已封裝爲_YYModelPropertyMeta類型)數組
    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;
    /// 要轉換的屬性個數,等於_allPropertyMetas.count
    NSUInteger _keyMappedCount;
    /// Model class type.
    YYEncodingNSType _nsType;

    /** 如下bool值,是根據咱們是否在model 類中實現了對應的自定義轉換方法來判斷的,
    好比若是咱們實現了`- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic`,
    那麼`_hasCustomTransformFromDictionary`的值就是YES了。
*/
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}

Tip:_mapper 和 _allPropertyMetas中存儲的是要轉換的屬性。它是受黑名單、白名單、setter 和getter 影響的。
假若有一個Model,有20個屬性,其中有5個是不須要轉換的,已經添加在黑名單方法中。那麼_mapper和 _allPropertyMetas中,實際上只有15個屬性。
若是咱們實現了白名單方法,裏面只寫了兩個屬性,那麼_mapper 和 _allPropertyMetas中就只有這兩個屬性了。
若是白名單裏的連個屬性都是隻讀的,那麼_allPropertyMetas中就沒有屬性了。
白名單 和黑名單方法是協議方法,分別是modelPropertyWhitelistmodelPropertyBlacklist,白名單中是要轉換的屬性名數組,黑名單中是不轉換的屬性名數組。spa

好了,如今能夠來看看[_YYModelMeta metaWithClass:cls]了,我來加一些註釋說明線程

+ (instancetype)metaWithClass:(Class)cls {
    if (!cls) return nil;
    // 一、聲明一個字典,用來存Model 類和類的信息,鍵是Model 名。
    static CFMutableDictionaryRef cache;
    static dispatch_once_t onceToken;
    // 二、聲明一個信號量
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        // 不要被這個方法唬住,它其實就是一個建立可變字典的方法,只不過是底層CF方法罷了。
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        // 爲信號量設置資源併發數
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    // 三、先從以前的字典中取要解析的Model 的類信息
    _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;
}

Tips

  1. 關於信號量,能夠去看GCD API 記錄 (三)中的8.dispatch_semaphore
  2. 咱們能夠用經常使用的API來寫一下這個方法,可能更容易理解:
+ (instancetype)metaWithClass:(Class)cls
{
    if (!cls) return nil;
    static NSMutableDictionary *cache;
    static dispatch_once_t onceToken;
    static NSLock *lock;
    dispatch_once(&onceToken, ^{
        cache = [[NSMutableDictionary alloc] init];
        lock = [[NSLock alloc] init];
    });

    [lock lock];
    _YYModelMeta *meta = [cache valueForKey:cls];
    [lock unlock];
    // 這裏的meta._classInfo.needUpdate可能有點問題,由於_classInfo是示例變量,並能用點語法取到,要注意。
    if (!meta || meta._classInfo.needUpdate) {
        meta = [[_YYModelMeta alloc] initWithClass:cls];
        if (meta) {
            [lock lock];
            [cache setValue:meta forKey:cls];
            [lock unlock];
        }
    }

    return meta;
}

第三步

這裏就是要解析第二步裏的重中之重[[_YYModelMeta alloc] initWithClass:cls];了。
因爲_YYModelMeta中的initWithClass代碼比較長(有150行),我就不在這裏貼出來了。寫一下僞代碼:

- (instancetype)initWithClass:(Class)cls {
    // 1.從Class中獲取類信息,並封裝成對象(這裏是重中之重)
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];

    // 2.獲取黑名單
    NSSet *blackList = [cls getBlackList];

    // 3.獲取白名單
    NSSet *whiteList = [cls getWhiteList];

    // 4.獲取容器類屬性以及對應Class 的字典
    NSDictionary *genericMapper = [cls getContainerGenericMapper];

    // 5.獲取要解析的屬性,包括排除黑名單、驗證白名單、驗證是否有getter 和setter 等。
    NSDictionary *allPropertyMetas = [cls getAllPropertyMetas];

    // 6.若是有屬性名和json中的鍵不同的,爲屬性設置json中的key,也有多是keyPath。
    NSDictionary *mapper = [cls handlerCustomMapper:allPropertyMetas];

    // 7.將allPropertyMetas中剩下的值添加到mapper中。(由於可能只有部分屬性名和json中的鍵不一致,設置以後會從allPropertyMetas刪除)
    [mapper setKeyValuesFrom:allPropertyMetas];

    // 8.其餘屬性賦值
    _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:)]);
}

用上面的僞代碼,將initWithClass簡化以後,就很容易理解了。

第四步

第三步中的重點就是YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];裏的實現,YYModel 裏經過runtime 獲取Model 的全部屬性,也就是在這個方法中作的。下面咱們來看看這個方法的實現代碼:

// 看過步驟二,用經常使用的API重寫過的方法再來看這個方法,應該就是So easy!
+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    // 1.聲明一個類緩存字典
    static CFMutableDictionaryRef classCache;
    /** 2.聲明一個元類緩存字典,這裏爲何要聲明一個元組緩存字典呢?
      由於YYClassInfo,有一個屬性superClassInfo,也是YYClassInfo類型的,也
      要使用這個方法來實例化,因此屢次迭代後,可能cls 就是元類了。
   */
    static CFMutableDictionaryRef metaCache;
    static dispatch_once_t onceToken;
    // 3.聲明一個信號量,線程安全會用到
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
         // 4.初始化類緩存字典
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        // 5.初始化元類緩存字典
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    // 6.從緩存中獲取類信息
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    // 7.若是能取到,可是須要更新,則利用runtime更新一下
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    // 8.若是沒獲取到,則根據cls 初始化一個類信息對象
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];
        if (info) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            // 9.將類信息保存到緩存字典中
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
}

經過上面的代碼以及我添加的註釋,應該就很容易理解這個方法的每一步了。

接下來就是全部json 轉換model 的精髓了,也是在文章開頭已經展現過的代碼了。
若是對利用runtime 獲取屬性列表等不太瞭解的,能夠去看Runtime系列(二)--Runtime的使用場景 中的1.運行時獲取某個類的屬性或函數

- (void)_update {
    // 1.先將一些實例變量置空
    _ivarInfos = nil;
    _methodInfos = nil;
    _propertyInfos = nil;

    Class cls = self.cls;
    unsigned int methodCount = 0;
    // 2.獲取方法列表
    Method *methods = class_copyMethodList(cls, &methodCount);
    if (methods) {
        NSMutableDictionary *methodInfos = [NSMutableDictionary new];
        _methodInfos = methodInfos;
        for (unsigned int i = 0; i < methodCount; i++) {
            // 2.1對Method 作了一層封裝,封裝成了YYClassMethodInfo
            YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
            if (info.name) methodInfos[info.name] = info;
        }
        free(methods);
    }
    unsigned int propertyCount = 0;
    // 3.獲取屬性列表
    objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
    if (properties) {
        NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i < propertyCount; i++) {
            // 對Property作了一層封裝,封裝成了YYClassPropertyInfo
            YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (info.name) propertyInfos[info.name] = info;
        }
        free(properties);
    }

    unsigned int ivarCount = 0;
    // 4.獲取示例變量列表
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars) {
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
           // 對示例變量作了一層封裝,封裝成了YYClassIvarInfo
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }

    if (!_ivarInfos) _ivarInfos = @{};
    if (!_methodInfos) _methodInfos = @{};
    if (!_propertyInfos) _propertyInfos = @{};

    _needUpdate = NO;
}

可能值得一提的還有這個方法:

// 這個方法其實就是利用runtime 爲一些屬性賦值,可能須要遞歸調用而已
- (instancetype)initWithClass:(Class)cls {
    if (!cls) return nil;
    self = [super init];
    _cls = cls;
    _superCls = class_getSuperclass(cls);
    _isMeta = class_isMetaClass(cls);
    if (!_isMeta) {
        _metaCls = objc_getMetaClass(class_getName(cls));
    }
    _name = NSStringFromClass(cls);
    [self _update];

    _superClassInfo = [self.class classInfoWithClass:_superCls];
    return self;
}

第五步

看完上面這四步,對於YYModel 是如何獲取屬性、方法、實例變量等都應該已經清楚了。如今要繼續回到步驟二中的方法了。
該方法中還有一個須要重點理解的方法[one yy_modelSetWithDictionary:dictionary],model 中全部屬性的賦值,都是在這個方法中實現的。下面來理解一下這個方法:

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    // 1.校驗dic
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;

    /** 獲取類信息,這裏若是使用的是原來的類,則實際上是從緩存中取出來的,由於在前面已經調用過metaWithClass方法了。
      若是是設置了轉換的類,則可能會再從新完整執行一次metaWithClass。
    */
    _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;
    }

    // 2.自定義的一個context 結構體,把model 的信息、model 對象指針、以及參數字典賦值上
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);


    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        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 {
         // 若是轉換屬性個數小於字典裏個鍵值對個數,
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }

    // 最後,若是有一些特殊的屬性,須要本身轉換賦值的話,再處理一下
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

對於上面的代碼,可能有困惑的是CFArrayApplyFunctionCFArrayApplyFunction這個函數。
在官方文檔裏有解釋:

// Calls a function once for each key-value pair in a dictionary.
// 對於字典裏的每個鍵值對,都會調用一次applier 方法
void CFDictionaryApplyFunction(CFDictionaryRef theDict, CFDictionaryApplierFunction applier, void *context);

//Calls a function once for each element in range in an array。
// 對於數組中指定range返回的每個元素調用一次applier
void CFArrayApplyFunction(CFArrayRef theArray, CFRange range, CFArrayApplierFunction applier, void *context);

看完註釋,再來看一下YYModel 中的實現:

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
  // 1.從上下文中取到model 的信息
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    // 2.從轉換字典中取到屬性對象
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    __unsafe_unretained id model = (__bridge id)(context->model);
  // 3.以防有多個相同key 的不一樣值
    while (propertyMeta) {
        if (propertyMeta->_setter) {
            // 爲model 的該屬性賦值。
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

ModelSetValueForProperty方法中會根據屬性的類型調用objc_msgSend來賦相應類型的值。
例如字符串類型的賦值:

if (meta->_nsType == YYEncodingTypeNSString) {
    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
 }

ModelSetWithPropertyMetaArrayFunction 與字典的處理方式相似,只不過applier 中的參數直接就是屬性對象罷了。

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);
    if (!propertyMeta->_setter) return;
    id value = nil;

    // 這裏由於value 的值,對象的可能有keyPath,也有直接的key。因此用不一樣的方式來取value
    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) {
        // 獲取model 的指針
        __unsafe_unretained id model = (__bridge id)(context->model);
        // 這裏就是爲model 賦值啦
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

到這裏YYModel 的解析就完畢啦。

相關文章
相關標籤/搜索