MJExtension源碼學習(一)

繼續進行優秀開源框架的源碼學習,此次打算學習一些經常使用的model解析的框架,好比YYModel,MJExtension,Mantle等。我本身用過YYModel和MJExtension,比較簡單易用,看過別人用Mantle的代碼,我的感受稍微繁瑣一些,因此此次就先學習MJExtension吧。json

本次的學習我分爲了兩個過程:api

  1. 初步瞭解MJExtension的原理,並經過初版的代碼學習基本的邏輯和思路。
  2. 閱讀學習新版本的代碼,加深對MJExtension的認識。

本文主要是記錄第一個過程當中的學習和心得。數組

MJExtension從最初到如今,也已經更新了幾十個版本了。因此在開始以前,咱們先查閱一些別的資料,從大概上來了解一下MJExtension的實現原理和一些學習的點。框架

參考文章post

就拿最簡單的json轉model來講,個人我的觀點,其實主要是運行時機制遞歸思想相結合來實現。經過運行時機制,咱們能夠獲取到一個類的全部屬性,而後經過遍從來對每個屬性進行賦值,若是該屬性又是一個自定義的類,那就用到遞歸的思想這樣一級級的解析下去,直到解析完成。數組也是同樣的,只是多了一個對數組遍歷的環節。學習

如今讓咱們具體來看MJExtension初版本的代碼(如下所提到MJExtension都是指的它的初版,特殊狀況會單獨指出)優化

MJExtension中最主要的就是NSObject+MJKeyValue這個類,他經過分類的形式向咱們提供了dict->model的方法,而後其中重要的方法- (instancetype)setKeyValues:(NSDictionary *)keyValuesdebug

賦值部分

下面是其中的代碼: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以後,遍歷全部的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學習的第一步就先到這,慢慢我會繼續看它的新的代碼,學習他的一些優化和封裝。

相關文章
相關標籤/搜索