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中就沒有屬性了。
白名單 和黑名單方法是協議方法,分別是modelPropertyWhitelist
和modelPropertyBlacklist
,白名單中是要轉換的屬性名數組,黑名單中是不轉換的屬性名數組。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 :
- 關於信號量,能夠去看GCD API 記錄 (三)中的8.dispatch_semaphore
- 咱們能夠用經常使用的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; }
對於上面的代碼,可能有困惑的是CFArrayApplyFunction
和 CFArrayApplyFunction
這個函數。
在官方文檔裏有解釋:
// 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 的解析就完畢啦。