繼續進行優秀開源框架的源碼學習,此次打算學習一些經常使用的model解析的框架,好比YYModel,MJExtension,Mantle等。我本身用過YYModel和MJExtension,比較簡單易用,看過別人用Mantle的代碼,我的感受稍微繁瑣一些,因此此次就先學習MJExtension吧。json
本次的學習我分爲了兩個過程:api
本文主要是記錄第一個過程當中的學習和心得。數組
MJExtension從最初到如今,也已經更新了幾十個版本了。因此在開始以前,咱們先查閱一些別的資料,從大概上來了解一下MJExtension的實現原理和一些學習的點。框架
參考文章post
就拿最簡單的json轉model來講,個人我的觀點,其實主要是運行時機制和遞歸思想相結合來實現。經過運行時機制,咱們能夠獲取到一個類的全部屬性,而後經過遍從來對每個屬性進行賦值,若是該屬性又是一個自定義的類,那就用到遞歸的思想這樣一級級的解析下去,直到解析完成。數組也是同樣的,只是多了一個對數組遍歷的環節。學習
如今讓咱們具體來看MJExtension初版本的代碼(如下所提到MJExtension都是指的它的初版,特殊狀況會單獨指出)優化
MJExtension中最主要的就是NSObject+MJKeyValue
這個類,他經過分類的形式向咱們提供了dict->model的方法,而後其中重要的方法- (instancetype)setKeyValues:(NSDictionary *)keyValues
debug
下面是其中的代碼:code
- (instancetype)setKeyValues:(NSDictionary *)keyValues { MJAssert2([keyValues isKindOfClass:[NSDictionary class]], self); [[self class] enumerateIvarsWithBlock:^(MJIvar *ivar, BOOL *stop) { // 1.取出屬性值 id value = keyValues ; for (NSString *key in ivar.keys) { value = value[key]; } if (!value || value == [NSNull null]) return; // 2.若是是模型屬性 MJType *type = ivar.type; Class typeClass = type.typeClass; if (!type.isFromFoundation && typeClass) { value = [typeClass objectWithKeyValues:value]; } else if (typeClass == [NSString class]) { if ([value isKindOfClass:[NSNumber class]]) { // NSNumber -> NSString value = [_numberFormatter stringFromNumber:value]; } else if ([value isKindOfClass:[NSURL class]]) { // NSURL -> NSString value = [value absoluteString]; } } else if ([value isKindOfClass:[NSString class]]) { if (typeClass == [NSNumber class]) { // NSString -> NSNumber value = [_numberFormatter numberFromString:value]; } else if (typeClass == [NSURL class]) { // NSString -> NSURL value = [NSURL URLWithString:value]; } } else if (ivar.objectClassInArray) { // 3.字典數組-->模型數組 value = [ivar.objectClassInArray objectArrayWithKeyValuesArray:value]; } // 4.賦值 [ivar setValue:value forObject:self]; }]; // 轉換完畢 if ([self respondsToSelector:@selector(keyValuesDidFinishConvertingToObject)]) { [self keyValuesDidFinishConvertingToObject]; } return self; }
能夠看的出這個方法裏面的主要代碼就是enumerateIvarsWithBlock
回調裏面的代碼,回調中返回了一個MJIvar
的類,MJIvar其實就是對你的model類裏面的每個成員變量作的進一步的封裝,封裝後每個成員變量對應對封裝成一個MJIvar的實例。component
MJIvar中的大多數字段都是起到了一個標識和記錄的做用,好比說成員變量的名、屬於哪一個類等,其中還包含一個MJType類型的屬性,其實也是作一些標識的做用,你們點進去看看就一目瞭然了。
作這一層的封裝主要是爲了以後的處理值時使用。
上述代碼的中間部分大篇的if,else if的判斷就是在作值的分類處理
MJType *type = ivar.type; Class typeClass = type.typeClass; if (!type.isFromFoundation && typeClass) { value = [typeClass objectWithKeyValues:value]; }
這第一個判斷,就是用於若是model中的某個成員變量仍是一個自定義類的狀況,type中的isFromFoundation字段就是標識改爲員變量的類是不是自定義的類,若是是自定義的類,把這個類存進type.typeClass下面。value = [typeClass objectWithKeyValues:value];
這句代碼也就是遞歸思想的提現,若是這個成員變量是一個自定義類的,那麼該成員變量對應的值應該也是一個model,因此用這個二級的model類繼續調用objectWithKeyValues
方法,繼續解析下去。
若是是數組,調用objectArrayWithKeyValuesArray
這個方法,原理相同,只是多一層的遍歷。
全部的解析,最後都調用了
// 4.賦值 [ivar setValue:value forObject:self];
這個是MJIvar中的方法
- (void)setValue:(id)value forObject:(id)object { if (_type.KVCDisabled) return; [object setValue:value forKey:_propertyName]; }
這裏_propertyName的也是MJIvar的一個字段,就是記錄封裝成MJIvar以前的這個成員變量的名字,而後使用setValue:forKey
爲一個類的成員變量賦值。
上面的是全部的成員變量已經封裝成MJIvar以後,遍歷全部的MJIvar並最終賦值的過程,還有一個方法也很重要,他實現了獲取全部的成員變量並封裝的這個過程的。下面看+ (void)enumerateIvarsWithBlock:(MJIvarsBlock)block
這個方法
+ (void)enumerateIvarsWithBlock:(MJIvarsBlock)block { static const char MJCachedIvarsKey; // 得到成員變量 NSMutableArray *cachedIvars = objc_getAssociatedObject(self, &MJCachedIvarsKey); if (cachedIvars == nil) { cachedIvars = [NSMutableArray array]; [self enumerateClassesWithBlock:^(__unsafe_unretained Class c, BOOL *stop) { // 1.得到全部的成員變量 unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); // 2.遍歷每個成員變量 for (unsigned int i = 0; i<outCount; i++) { MJIvar *ivar = [MJIvar cachedIvarWithIvar:ivars[i]]; ivar.key = [self ivarKey:ivar.propertyName]; // 若是有多級映射 ivar.keys = [ivar.key componentsSeparatedByString:@"."]; // 數組中的模型類 ivar.objectClassInArray = [self ivarObjectClassInArray:ivar.propertyName]; ivar.srcClass = c; [cachedIvars addObject:ivar]; } // 3.釋放內存 free(ivars); }]; objc_setAssociatedObject(self, &MJCachedIvarsKey, cachedIvars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } // 遍歷成員變量 BOOL stop = NO; for (MJIvar *ivar in cachedIvars) { block(ivar, &stop); if (stop) break; } }
你們能夠看到,它主要的部分,又是一個遍歷以後的回調, 咱們那順便貼出來這個遍歷的代碼
+ (void)enumerateClassesWithBlock:(MJClassesBlock)block { // 1.沒有block就直接返回 if (block == nil) return; // 2.中止遍歷的標記 BOOL stop = NO; // 3.當前正在遍歷的類 Class c = self; // 4.開始遍歷每個類 while (c && !stop) { // 4.1.執行操做 block(c, &stop); // 4.2.得到父類 c = class_getSuperclass(c); if ([MJFoundation isClassFromFoundation:c]) break; } }
很容易看出來,這是爲了處理那種父類也是自定義類的狀況,那咱們實際開發中來講,通常後臺返回的數據都是有固定形式的,好比說status,message,code這種字段是每一個接口都返回的,因此這些字段我通常都寫在一個父類裏面,而後這個循環就是遍歷出父類,爲從父類中繼承的字段賦值。
說回上一個遍歷方法,其實就是一個封裝過程,從代碼中能夠看出來,使用了一些runtime
的api來獲取了類的成員變量,而後經過循環對每一個變量進行了封裝。這一步的話,光這樣幹很難體驗什麼,你們能夠跑一個簡單的例子,而後debug跟一下,看看MJIvar中每一個屬性表明什麼。
有些狀況可能要映射字段名,好比id屬於關鍵字,可能公司要求model中不容許用id做爲成員變量名, 因此要作映射處理,還有若是數組中若是包含別的model,這個組數中的model類名咱們也應該告訴MJExtension。
MJExtension都拋出了方法,須要映射名稱使用replacedKeyFromPropertyName
,數組包含模型使用objectClassInArray
,咱們根據本身的須要的重寫相應方法。
舉個例子:
成員變量屬因而數組的狀況下, 這個數組裏麪包含model類型要存在ivar.objectClassInArray下面
// 數組中的模型類 ivar.objectClassInArray = [self ivarObjectClassInArray:ivar.propertyName];
這是ivarObjectClassInArray
的實現
+ (Class)ivarObjectClassInArray:(NSString *)propertyName { if ([self respondsToSelector:@selector(objectClassInArray)]) { return self.objectClassInArray[propertyName]; } else { // 爲了兼容之前的對象方法 id tempObject = self.tempObject; if ([tempObject respondsToSelector:@selector(objectClassInArray)]) { id dict = [tempObject objectClassInArray]; return dict[propertyName]; } return nil; } return nil; }
在賦值的時候會先判斷是否respondsToSelector
,而後根據狀況賦值。
初版的代碼比較簡單,我就簡單的dict->model說了一下,model->dict你們能夠本身再去看看,固然其餘還有一些細節處理的東西, 你們也能夠經過代碼來進一步學習。
對MJExtension學習的第一步就先到這,慢慢我會繼續看它的新的代碼,學習他的一些優化和封裝。