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