MJExtension源碼學(xué)習(xí)(一)

繼續(xù)進(jìn)行優(yōu)秀開源框架的源碼學(xué)習(xí)绑蔫,這次打算學(xué)習(xí)一些常用的model解析的框架柄驻,比如YYModel,MJExtension沟启,Mantle等。我自己用過YYModel和MJExtension犹菇,比較簡(jiǎn)單易用德迹,看過別人用Mantle的代碼,個(gè)人感覺稍微繁瑣一些项栏,所以這次就先學(xué)習(xí)MJExtension吧浦辨。

本次的學(xué)習(xí)我分為了兩個(gè)過程:

  1. 初步了解MJExtension的原理,并通過第一版的代碼學(xué)習(xí)基本的邏輯和思路沼沈。
  2. 閱讀學(xué)習(xí)新版本的代碼流酬,加深對(duì)MJExtension的認(rèn)識(shí)。

本文主要是記錄第一個(gè)過程中的學(xué)習(xí)和心得列另。

MJExtension從最初到現(xiàn)在芽腾,也已經(jīng)更新了幾十個(gè)版本了。所以在開始之前页衙,我們先查閱一些別的資料摊滔,從大概上來(lái)了解一下MJExtension的實(shí)現(xiàn)原理和一些學(xué)習(xí)的點(diǎn)阴绢。

參考文章

就拿最簡(jiǎn)單的json轉(zhuǎn)model來(lái)說,我的個(gè)人觀點(diǎn)艰躺,其實(shí)主要是運(yùn)行時(shí)機(jī)制遞歸思想相結(jié)合來(lái)實(shí)現(xiàn)呻袭。通過運(yùn)行時(shí)機(jī)制,我們可以獲取到一個(gè)類的所有屬性腺兴,然后通過遍歷來(lái)對(duì)每一個(gè)屬性進(jìn)行賦值左电,如果該屬性又是一個(gè)自定義的類,那就用到遞歸的思想這樣一級(jí)級(jí)的解析下去页响,直到解析完成篓足。數(shù)組也是一樣的,只是多了一個(gè)對(duì)數(shù)組遍歷的環(huán)節(jié)闰蚕。

現(xiàn)在讓我們具體來(lái)看MJExtension第一版本的代碼(以下所提到MJExtension都是指的它的第一版栈拖,特殊情況會(huì)單獨(dú)指出)

MJExtension中最主要的就是NSObject+MJKeyValue這個(gè)類,他通過分類的形式向我們提供了dict->model的方法没陡,然后其中重要的方法- (instancetype)setKeyValues:(NSDictionary *)keyValues

賦值部分

下面是其中的代碼:

- (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.字典數(shù)組-->模型數(shù)組
            value = [ivar.objectClassInArray objectArrayWithKeyValuesArray:value];
        }
        
        // 4.賦值
        [ivar setValue:value forObject:self];
    }];
    
    // 轉(zhuǎn)換完畢
    if ([self respondsToSelector:@selector(keyValuesDidFinishConvertingToObject)]) {
        [self keyValuesDidFinishConvertingToObject];
    }
    
    return self;
}

可以看的出這個(gè)方法里面的主要代碼就是enumerateIvarsWithBlock 回調(diào)里面的代碼涩哟,回調(diào)中返回了一個(gè)MJIvar的類,MJIvar其實(shí)就是對(duì)你的model類里面的每一個(gè)成員變量做的進(jìn)一步的封裝诗鸭,封裝后每一個(gè)成員變量對(duì)應(yīng)對(duì)封裝成一個(gè)MJIvar的實(shí)例染簇。

MJIvar中的大多數(shù)字段都是起到了一個(gè)標(biāo)識(shí)和記錄的作用,比如說成員變量的名强岸、屬于哪個(gè)類等锻弓,其中還包含一個(gè)MJType類型的屬性,其實(shí)也是做一些標(biāo)識(shí)的作用蝌箍,大家點(diǎn)進(jìn)去看看就一目了然了青灼。

做這一層的封裝主要是為了之后的處理值時(shí)使用。

上述代碼的中間部分大篇的if妓盲,else if的判斷就是在做值的分類處理

MJType *type = ivar.type;
Class typeClass = type.typeClass;
if (!type.isFromFoundation && typeClass) {
   value = [typeClass objectWithKeyValues:value];
}

這第一個(gè)判斷杂拨,就是用于如果model中的某個(gè)成員變量還是一個(gè)自定義類的情況,type中的isFromFoundation字段就是標(biāo)識(shí)改成員變量的類是否是自定義的類悯衬,如果是自定義的類弹沽,把這個(gè)類存進(jìn)type.typeClass下面。
value = [typeClass objectWithKeyValues:value];
這句代碼也就是遞歸思想的提現(xiàn)筋粗,如果這個(gè)成員變量是一個(gè)自定義類的策橘,那么該成員變量對(duì)應(yīng)的值應(yīng)該也是一個(gè)model,所以用這個(gè)二級(jí)的model類繼續(xù)調(diào)用objectWithKeyValues方法娜亿,繼續(xù)解析下去丽已。

如果是數(shù)組,調(diào)用objectArrayWithKeyValuesArray這個(gè)方法买决,原理相同沛婴,只是多一層的遍歷吼畏。

所有的解析,最后都調(diào)用了

// 4.賦值
[ivar setValue:value forObject:self];

這個(gè)是MJIvar中的方法

- (void)setValue:(id)value forObject:(id)object
{
    if (_type.KVCDisabled) return;
    [object setValue:value forKey:_propertyName];
}

這里_propertyName的也是MJIvar的一個(gè)字段嘁灯,就是記錄封裝成MJIvar之前的這個(gè)成員變量的名字泻蚊,然后使用
setValue:forKey為一個(gè)類的成員變量賦值。

獲取類的成員變量旁仿,封裝MJIvar

上面的是所有的成員變量已經(jīng)封裝成MJIvar之后藕夫,遍歷所有的MJIvar并最終賦值的過程,還有一個(gè)方法也很重要枯冈,他實(shí)現(xiàn)了獲取所有的成員變量并封裝的這個(gè)過程的。下面看+ (void)enumerateIvarsWithBlock:(MJIvarsBlock)block這個(gè)方法

+ (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.遍歷每一個(gè)成員變量
            for (unsigned int i = 0; i<outCount; i++) {
                MJIvar *ivar = [MJIvar cachedIvarWithIvar:ivars[i]];
                ivar.key = [self ivarKey:ivar.propertyName];
                // 如果有多級(jí)映射
                ivar.keys = [ivar.key componentsSeparatedByString:@"."];
                // 數(shù)組中的模型類
                ivar.objectClassInArray = [self ivarObjectClassInArray:ivar.propertyName];
                ivar.srcClass = c;
                [cachedIvars addObject:ivar];
            }
            
            // 3.釋放內(nèi)存
            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;
    }
}

大家可以看到办悟,它主要的部分尘奏,又是一個(gè)遍歷之后的回調(diào), 我們那順便貼出來(lái)這個(gè)遍歷的代碼

+ (void)enumerateClassesWithBlock:(MJClassesBlock)block
{
    // 1.沒有block就直接返回
    if (block == nil) return;
    
    // 2.停止遍歷的標(biāo)記
    BOOL stop = NO;
    
    // 3.當(dāng)前正在遍歷的類
    Class c = self;
    
    // 4.開始遍歷每一個(gè)類
    while (c && !stop) {
        // 4.1.執(zhí)行操作
        block(c, &stop);
        
        // 4.2.獲得父類
        c = class_getSuperclass(c);
        
        if ([MJFoundation isClassFromFoundation:c]) break;
    }
}

很容易看出來(lái)病蛉,這是為了處理那種父類也是自定義類的情況炫加,那我們實(shí)際開發(fā)中來(lái)說,一般后臺(tái)返回的數(shù)據(jù)都是有固定形式的铺然,比如說status俗孝,message,code這種字段是每個(gè)接口都返回的魄健,所以這些字段我一般都寫在一個(gè)父類里面赋铝,然后這個(gè)循環(huán)就是遍歷出父類,為從父類中繼承的字段賦值沽瘦。

說回上一個(gè)遍歷方法革骨,其實(shí)就是一個(gè)封裝過程,從代碼中可以看出來(lái)析恋,使用了一些runtime
的api來(lái)獲取了類的成員變量良哲,然后通過循環(huán)對(duì)每個(gè)變量進(jìn)行了封裝。這一步的話助隧,光這樣干很難體驗(yàn)什么筑凫,大家可以跑一個(gè)簡(jiǎn)單的例子,然后debug跟一下并村,看看MJIvar中每個(gè)屬性代表什么巍实。

映射問題

有些情況可能要映射字段名,比如id屬于關(guān)鍵字橘霎,可能公司要求model中不允許用id作為成員變量名蔫浆, 所以要做映射處理,還有如果數(shù)組中如果包含別的model姐叁,這個(gè)組數(shù)中的model類名我們也應(yīng)該告訴MJExtension瓦盛。

MJExtension都拋出了方法洗显,需要映射名稱使用replacedKeyFromPropertyName,數(shù)組包含模型使用objectClassInArray,我們根據(jù)自己的需要的重寫相應(yīng)方法。

舉個(gè)例子:

成員變量屬于是數(shù)組的情況下原环, 這個(gè)數(shù)組里面包含model類型要存在ivar.objectClassInArray下面

// 數(shù)組中的模型類
ivar.objectClassInArray = [self ivarObjectClassInArray:ivar.propertyName];

這是ivarObjectClassInArray的實(shí)現(xiàn)

+ (Class)ivarObjectClassInArray:(NSString *)propertyName
{
    if ([self respondsToSelector:@selector(objectClassInArray)]) {
        return self.objectClassInArray[propertyName];
    } else {
        // 為了兼容以前的對(duì)象方法
        id tempObject = self.tempObject;
        if ([tempObject respondsToSelector:@selector(objectClassInArray)]) {
            id dict = [tempObject objectClassInArray];
            return dict[propertyName];
        }
        return nil;
    }
    return nil;
}

在賦值的時(shí)候會(huì)先判斷是否respondsToSelector挠唆,然后根據(jù)情況賦值。

第一版的代碼比較簡(jiǎn)單嘱吗,我就簡(jiǎn)單的dict->model說了一下玄组,model->dict大家可以自己再去看看,當(dāng)然其他還有一些細(xì)節(jié)處理的東西谒麦, 大家也可以通過代碼來(lái)進(jìn)一步學(xué)習(xí)俄讹。

對(duì)MJExtension學(xué)習(xí)的第一步就先到這,慢慢我會(huì)繼續(xù)看它的新的代碼绕德,學(xué)習(xí)他的一些優(yōu)化和封裝患膛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耻蛇,隨后出現(xiàn)的幾起案子踪蹬,更是在濱河造成了極大的恐慌,老刑警劉巖臣咖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跃捣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夺蛇,警方通過查閱死者的電腦和手機(jī)疚漆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蚊惯,“玉大人愿卸,你說我怎么就攤上這事〗匦停” “怎么了趴荸?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宦焦。 經(jīng)常有香客問我发钝,道長(zhǎng),這世上最難降的妖魔是什么波闹? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任酝豪,我火速辦了婚禮,結(jié)果婚禮上精堕,老公的妹妹穿的比我還像新娘孵淘。我一直安慰自己,他們只是感情好歹篓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布瘫证。 她就那樣靜靜地躺著揉阎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪背捌。 梳的紋絲不亂的頭發(fā)上毙籽,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音毡庆,去河邊找鬼坑赡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛么抗,可吹牛的內(nèi)容都是我干的毅否。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乖坠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搀突!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起熊泵,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甸昏,沒想到半個(gè)月后顽分,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡施蜜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年卒蘸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翻默。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缸沃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出修械,到底是詐尸還是另有隱情趾牧,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布肯污,位于F島的核電站翘单,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蹦渣。R本人自食惡果不足惜哄芜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柬唯。 院中可真熱鬧认臊,春花似錦、人聲如沸锄奢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至师坎,卻和暖如春恕酸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胯陋。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蕊温, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遏乔。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓义矛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親盟萨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凉翻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,385評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,101評(píng)論 1 32
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法捻激,內(nèi)部類的語(yǔ)法制轰,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法胞谭,線程的語(yǔ)...
    子非魚_t_閱讀 31,631評(píng)論 18 399
  • 一直以為能夠讀懂源代碼是件很牛的事情,但是每次都被動(dòng)輒復(fù)雜的語(yǔ)法的架構(gòu)嚇跑,在偶然看到一個(gè)叫Draveness的大...
    VoyageCN閱讀 979評(píng)論 1 2
  • 第一章聽和講 我想問題大都在于發(fā)音垃杖;你不知道正確的發(fā)音,或者雖然知道而你的耳卻不行丈屹。 聽:“聽不懂”→正確的發(fā)音 ...
    milaji閱讀 633評(píng)論 0 0