YYModel 源碼導(dǎo)讀

YYModel 是一個把 Json 數(shù)據(jù)轉(zhuǎn)換成 model 的一個輕量級工具用狱。本文將深入源碼來談?wù)刌YModel是如何實(shí)現(xiàn) Json->Model 的金拒。建議讀者有運(yùn)行時的基礎(chǔ)。
運(yùn)行時是什么绅作?這里簡單概括一下郊楣。
運(yùn)行時:可以通過 runtime 的方法,在程序運(yùn)行的時候伸辟,獲取一個對象的所有信息。對象擁有的屬性馍刮,成員變量,對象可以響應(yīng)的方法窃蹋。以及父類的這些信息卡啰,一直到根類 NSObject。
從最簡單的+ (instancetype)modelWithJSON:(id)json;說起警没。

大步驟有兩步:

1.Json->Dict
2.Dict->Model

第一步Json->Dict

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json{...}

這一步看源碼比較簡單匈辱,不贅述。就是把 json轉(zhuǎn)成 OC 中的字典杀迹。

第二步Dict->Model

第二步主要里面分成2步
2.1 通過運(yùn)行時獲取 對象模型的類信息
2.2 用類的元信息來處理 dict->model
我們來看看有效代碼
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
    //依據(jù) 實(shí)例對象所屬類 生成 meta亡脸。  meta 是一個含有大量 cls 信息的對象。
    Class cls = [self class];                    
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];         

    //這一步可以暫時忽略,等所有的看完了浅碾,這個就會懂了大州。
    cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;

    //dict->model
    NSObject *one = [cls new];
    if ([one modelSetWithDictionary:dictionary]) return one; 
}

2.1獲取類的元信息。

_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
我們先進(jìn)到[_YYModelMeta metaWithClass:cls]里去垂谢。
我們直接看有效代碼厦画,YYModel 有很多巧妙的東西,到時候我另開一篇講解滥朱。

+ (instancetype)metaWithClass:(Class)cls {
     _YYModelMeta meta = [[_YYModelMeta alloc] initWithClass:cls]根暑;
    return meta;
}

??沒錯,你們看到一大坨代碼徙邻,其實(shí)可以簡單地描述成這么一句排嫌。略微有細(xì)節(jié)缺失。但重點(diǎn)就這一句缰犁。
進(jìn)到_YYModelMeta meta = [[_YYModelMeta alloc] initWithClass:cls]躏率;里面看看。我把白名單民鼓,黑名單薇芝,自定義映射的功能暫時去掉了。那些是對字典轉(zhuǎn)模型的補(bǔ)充丰嘉。我們先搞懂原理夯到,再去擴(kuò)展開來。

- (instancetype)initWithClass:(Class)cls {
    //依據(jù) cls 獲取YYClassInfo
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];

    // Create all property metas.
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;

    //這里發(fā)生了非常美妙的遞歸饮亏。判定條件耍贾,子類和父類都存在。然后去遍歷子類路幸,遍歷完子類荐开。把當(dāng)前類設(shè)置為 之前遍歷類的 父類。指導(dǎo)简肴,curClassInfo = NSObject
    while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
        for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
          _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
                                                                    propertyInfo:propertyInfo
                                                                         generic:genericMapper[propertyInfo.name]];
          allPropertyMetas[meta->_name] = meta;
          //這里通過遞歸給所以的屬性添加
        }
        curClassInfo = curClassInfo.superClassInfo;
    }

    //創(chuàng)建映射     
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        mapper[name] = propertyMeta;             //添加到字典里面
    }];
    //name = "<_YYModelPropertyMeta: 0x1700e8780>";
    //birthday = "<_YYModelPropertyMeta: 0x1700e8780>";

    return self;
}

上面的代碼總共分三步晃听。

1.根據(jù)類生成YYClassInfo
2.根據(jù)YYClassInfo去遍歷類,父類的屬性砰识。目的:生成所有屬性的_YYModelPropertyMeta能扒。
放到集合allPropertyMetas里面。
3.遍歷allPropertyMetas辫狼,生成常規(guī)的映射字典mapper初斑。

allPropertyMetas是數(shù)組,能不能用來映射需要去遍歷確認(rèn)膨处。
有些name:namePropertyMetas是以 keyPath见秤,multiKeys存在的砂竖。
并不直接生成常規(guī) mapper。這塊不理解可以先放放鹃答。

簡單地說最終產(chǎn)生了所有屬性的 key:keyPropertyMetas 

YYClassInfo是什么乎澄?上面的第一步就產(chǎn)生了這個。

@interface _YYModelMeta : NSObject {
    YYClassInfo *_classInfo;
    ......
}

YYClassInfo這個類可以理解成 實(shí)例對象 的類信息都存在里面了挣跋。舉個例子三圆。YYAuthor是一個繼承自NSObject的類。

@interface YYAuthor : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSDate *birthday;
@end
YYAuthor *author = [YYAuthor New];
把a(bǔ)uthor傳進(jìn)來避咆,會產(chǎn)生如下信息舟肉。

Class cls; ///< 模型是什么類.  此處是YYAuthor
Class superCls; ///< super class object  此處是NSObject  
Class metaCls;  ///YYAuthor是 metaCls的實(shí)例對象。就像author是YYAuthor的實(shí)例對象查库。
BOOL isMeta; ///< 當(dāng)前類是否是元類路媚。
NSString *name; ///< class name  此處是字符串YYAuthor
YYClassInfo *superClassInfo; ///< 父類的類信息。此處是 NSObject的信息

NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars  
//此處是 name:nameInfo樊销,birthday:birthdayInfo組成的字典信息

NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
//比如name的 set,get方法整慎。birthday的set,get方法等。畢竟@property是會自動生成 set,get 方法的围苫。

NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
//name:namePropertyInfo, birthday:brithdayPropertyInfo組成的字典
Snip20170215_7.png

至于YYClassInfo是如何產(chǎn)生的每個細(xì)節(jié)裤园。那真的是太繁瑣了。不過我 Github 上的代碼都做了注釋剂府。有興趣的可以看一下拧揽。對于一個 OC 對象是如何組成的,可以有非常深刻的理解腺占。(連接在文末)

2.2 用類的元信息來處理 dict->model

這一步反而格外簡單淤袜。

- (BOOL)modelSetWithDictionary:(NSDictionary *)dic {
  //依據(jù) self 獲得之前生成的_YYModelMeta。
  _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    
    //把參數(shù)塞進(jìn)結(jié)構(gòu)體衰伯。
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);       //_YYModelMeta
    context.model = (__bridge void *)(self);                //self    //這個好像并沒有用起來
    context.dictionary = (__bridge void *)(dic);            //oc字典铡羡。 //這個值傳入 context 在 json 少于map的情況下使用

    //遍歷Dict
    CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
}
1.獲取生成之前的_YYModelMeta
2.生成結(jié)構(gòu)體
3.遍歷字典

重點(diǎn)是ModelSetWithDictionaryFunction這個方法相當(dāng)于遍歷字典的 block。
OK,我們點(diǎn)進(jìn)方法意鲸,然后提取一下烦周。

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
     __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

屬性的 setter方法是否存在。存在則調(diào)用ModelSetValueForProperty临扮。
ModelSetValueForProperty總結(jié)一下就是用objc_msgSend給 模型 發(fā)送setter消息 參數(shù)為 value论矾。完成賦值!
YYModel 字典轉(zhuǎn)模型最基本的框架就是這樣的杆勇。我這里再總結(jié)一下!下面這個是簡單版本饱亿。

1.二進(jìn)制Json ->  OC Dictionary
2.OC Dictionary -> Model
    2.1 通過運(yùn)行時獲取類的元信息(_YYModelMeta)
        2.1.1 類的元信息需要獲取YYClassInfo
            2.1.1.1 YYClassInfo獲取
                2.1.1.1.1 class相關(guān)蚜退,父類闰靴,元類指針等
                2.1.1.1.1 YYClassMethods
                2.1.1.1.2 YYClassProperties
                2.1.1.1.3 YYClassIvarInfos
        2.1.2 根據(jù)YYClassInfo去遍歷類,父類的屬性钻注。
            2.1.2.1 依據(jù)屬性生成_YYModelPropertyMeta
            2.1.2.2 把_YYModelPropertyMeta添加到allPropertyMetas
        2.1.3 遍歷_YYModelPropertyMeta蚂且,生成常規(guī)的 mapper(這是一個dict)
    2.2 使用類的元信息發(fā)生字典轉(zhuǎn)模型
        2.2.1 獲取之前生成的 _YYModelMeta
        2.2.2 生成結(jié)構(gòu)體ModelSetContext context
        2.2.3 遍歷 OC Dictionary,context傳參
            2.2.3.1 以字典的 key 為 key 去嘗試獲取對應(yīng)的 _YYModelPropertyMeta
            2.2.3.2 如果有改_YYModelPropertyMeta幅恋,并且有setter 方法杏死。
                2.2.3.2.1 依據(jù)propertyMeta,
                用objc_msgSend給對象發(fā)送 setter 方法捆交。value是之前字典的 value淑翼。

接下來的是復(fù)雜版本的。更加全面品追!

1.二進(jìn)制Json ->  OC Dictionary
2.OC Dictionary -> Model
    2.1 通過運(yùn)行時獲取類的元信息(_YYModelMeta)
        2.1.1 類的元信息需要獲取YYClassInfo
            2.1.1.1 YYClassInfo獲取
                2.1.1.1.1 class相關(guān)玄括,父類,元類指針等
                2.1.1.1.1 YYClassMethods
                2.1.1.1.2 YYClassProperties
                2.1.1.1.3 YYClassIvarInfos
        2.1.2 獲取黑名單
        2.1.3 獲取白名單
        2.1.4 獲取自定義容易類型
        2.1.5 根據(jù)YYClassInfo去遍歷類肉瓦,父類的屬性遭京。
            2.1.5.1 該屬性在黑名單中則 跳出遍歷
            2.1.5.2 假如有白名單則必須在白名單中 不然跳出遍歷
            2.1.5.3 依據(jù)屬性生成_YYModelPropertyMeta  
            2.1.5.4 判定并把_YYModelPropertyMeta添加到allPropertyMetas
        2.1.6 如果完成了自定義映射。
            2.1.6.1 在局部allPropertyMetas中移除該屬性名
            2.1.6.2 依據(jù)不同的映射情況泞莉,填充_mappedToKey哪雕,_mappedToKeyPath,
            _mappedToKeyArray鲫趁。并添加到總 Mapper 里斯嚎。
        2.1.7 遍歷allPropertyMetas(此時比之前少了自定義的映射)
            2.1.7.1 簡單的依據(jù) name ,添加到 Mapper 中饮寞。
        2.1.8 把大量局部變量賦值到全局變量孝扛。立一些 flag。
    2.2 多態(tài):此處可以根據(jù)OC Dictionary更改當(dāng)前的類幽崩。一般在用來指向子類苦始。大部分情況不用。
    2.3 使用類的元信息發(fā)生字典轉(zhuǎn)模型
        2.3.1 獲取之前生成的 _YYModelMeta
        2.3.2 生成結(jié)構(gòu)體ModelSetContext context
        2.3.3 如果 模型屬性數(shù)量 大于 字典內(nèi)數(shù)量(那個少遍歷哪個)
            2.3.3.1 遍歷 OC Dictionary慌申,context傳參
                2.3.3.1 以字典的 key 為 key 去嘗試獲取對應(yīng)的 _YYModelPropertyMeta
                2.3.3.2 如果有改_YYModelPropertyMeta陌选,并且有setter 方法。
                    2.3.3.2.1 依據(jù)propertyMeta蹄溉,
                    用objc_msgSend給對象發(fā)送 setter 方法咨油。value是之前字典的 value。
            2.3.3.2 如果_keyPathPropertyMetas有值柒爵,遍歷其數(shù)組
            2.3.3.3 如果_multiKeysPropertyMetas有值役电,遍歷其數(shù)組
        2.3.4 遍歷模型屬性,數(shù)組遍歷棉胀。
        2.3.5 在模型轉(zhuǎn)換最后被調(diào)用法瑟,自己手動完成一些值的賦值冀膝,類似自定義映射

??本篇文章只是梳理了字典轉(zhuǎn)模型的框架,細(xì)節(jié)可以在下面這個項(xiàng)目中查看霎挟。
??我給 YYModel 做了一定程度的注釋窝剖。會有一些細(xì)節(jié)的缺失,我會持續(xù)更新的酥夭。
??至于 Model轉(zhuǎn)字典和運(yùn)行時copy,nscoding之類的赐纱。難度不大,有空可能會更新熬北。
??大家哪里不懂疙描,問問我唄,也讓我看看哪個點(diǎn)沒講清楚蒜埋。

源碼:https://github.com/PomTTcat/YYModelGuideRead_JEFF

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淫痰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子整份,更是在濱河造成了極大的恐慌待错,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烈评,死亡現(xiàn)場離奇詭異火俄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)讲冠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門瓜客,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人竿开,你說我怎么就攤上這事谱仪。” “怎么了否彩?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵疯攒,是天一觀的道長。 經(jīng)常有香客問我列荔,道長敬尺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任贴浙,我火速辦了婚禮砂吞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘崎溃。我一直安慰自己蜻直,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袭蝗,像睡著了一般唤殴。 火紅的嫁衣襯著肌膚如雪般婆。 梳的紋絲不亂的頭發(fā)上到腥,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音蔚袍,去河邊找鬼乡范。 笑死,一個胖子當(dāng)著我的面吹牛啤咽,可吹牛的內(nèi)容都是我干的晋辆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼宇整,長吁一口氣:“原來是場噩夢啊……” “哼瓶佳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳞青,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤霸饲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后臂拓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厚脉,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年胶惰,在試婚紗的時候發(fā)現(xiàn)自己被綠了傻工。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡孵滞,死狀恐怖中捆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坊饶,我是刑警寧澤泄伪,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站幼东,受9級特大地震影響臂容,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜根蟹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一脓杉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧简逮,春花似錦球散、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凌净。三九已至,卻和暖如春屋讶,著一層夾襖步出監(jiān)牢的瞬間冰寻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工皿渗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斩芭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓乐疆,卻偏偏與公主長得像划乖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挤土,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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