YYModel 是一個(gè)把 Json 數(shù)據(jù)轉(zhuǎn)換成 model 的一個(gè)輕量級(jí)工具。本文將深入源碼來(lái)談?wù)刌YModel是如何實(shí)現(xiàn) Json->Model 的。建議讀者有運(yùn)行時(shí)的基礎(chǔ)。
運(yùn)行時(shí)是什么?這里簡(jiǎn)單概括一下。
運(yùn)行時(shí):可以通過(guò) runtime 的方法,在程序運(yùn)行的時(shí)候,獲取一個(gè)對(duì)象的所有信息。對(duì)象擁有的屬性,成員變量,對(duì)象可以響應(yīng)的方法。以及父類的這些信息,一直到根類 NSObject。
從最簡(jiǎn)單的+ (instancetype)modelWithJSON:(id)json;說(shuō)起。
大步驟有兩步:
1.Json->Dict
2.Dict->Model
第一步Json->Dict
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json{...}
這一步看源碼比較簡(jiǎn)單,不贅述。就是把 json轉(zhuǎn)成 OC 中的字典。
第二步Dict->Model
第二步主要里面分成2步
2.1 通過(guò)運(yùn)行時(shí)獲取 對(duì)象模型的類信息
2.2 用類的元信息來(lái)處理 dict->model
我們來(lái)看看有效代碼
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
//依據(jù) 實(shí)例對(duì)象所屬類 生成 meta。 meta 是一個(gè)含有大量 cls 信息的對(duì)象。
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
//這一步可以暫時(shí)忽略,等所有的看完了,這個(gè)就會(huì)懂了。
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 有很多巧妙的東西,到時(shí)候我另開(kāi)一篇講解。
+ (instancetype)metaWithClass:(Class)cls {
_YYModelMeta meta = [[_YYModelMeta alloc] initWithClass:cls];
return meta;
}
??沒(méi)錯(cuò),你們看到一大坨代碼,其實(shí)可以簡(jiǎn)單地描述成這么一句。略微有細(xì)節(jié)缺失。但重點(diǎn)就這一句。
進(jìn)到_YYModelMeta meta = [[_YYModelMeta alloc] initWithClass:cls];里面看看。我把白名單,黑名單,自定義映射的功能暫時(shí)去掉了。那些是對(duì)字典轉(zhuǎn)模型的補(bǔ)充。我們先搞懂原理,再去擴(kuò)展開(kāi)來(lái)。
- (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;
//這里通過(guò)遞歸給所以的屬性添加
}
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ù)組,能不能用來(lái)映射需要去遍歷確認(rèn)。
有些name:namePropertyMetas是以 keyPath,multiKeys存在的。
并不直接生成常規(guī) mapper。這塊不理解可以先放放。
簡(jiǎn)單地說(shuō)最終產(chǎn)生了所有屬性的 key:keyPropertyMetas
YYClassInfo是什么?上面的第一步就產(chǎn)生了這個(gè)。
@interface _YYModelMeta : NSObject {
YYClassInfo *_classInfo;
......
}
YYClassInfo這個(gè)類可以理解成 實(shí)例對(duì)象 的類信息都存在里面了。舉個(gè)例子。YYAuthor是一個(gè)繼承自NSObject的類。
@interface YYAuthor : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSDate *birthday;
@end
YYAuthor *author = [YYAuthor New];
把a(bǔ)uthor傳進(jìn)來(lái),會(huì)產(chǎn)生如下信息。
Class cls; ///< 模型是什么類. 此處是YYAuthor
Class superCls; ///< super class object 此處是NSObject
Class metaCls; ///YYAuthor是 metaCls的實(shí)例對(duì)象。就像author是YYAuthor的實(shí)例對(duì)象。
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是會(huì)自動(dòng)生成 set,get 方法的。
NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
//name:namePropertyInfo, birthday:brithdayPropertyInfo組成的字典

至于YYClassInfo是如何產(chǎn)生的每個(gè)細(xì)節(jié)。那真的是太繁瑣了。不過(guò)我 Github 上的代碼都做了注釋。有興趣的可以看一下。對(duì)于一個(gè) OC 對(duì)象是如何組成的,可以有非常深刻的理解。(連接在文末)
2.2 用類的元信息來(lái)處理 dict->model
這一步反而格外簡(jiǎn)單。
- (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 //這個(gè)好像并沒(méi)有用起來(lái)
context.dictionary = (__bridge void *)(dic); //oc字典。 //這個(gè)值傳入 context 在 json 少于map的情況下使用
//遍歷Dict
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
}
1.獲取生成之前的_YYModelMeta
2.生成結(jié)構(gòu)體
3.遍歷字典
重點(diǎn)是ModelSetWithDictionaryFunction這個(gè)方法相當(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é)一下!下面這個(gè)是簡(jiǎn)單版本。
1.二進(jìn)制Json -> OC Dictionary
2.OC Dictionary -> Model
2.1 通過(guò)運(yùn)行時(shí)獲取類的元信息(_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(這是一個(gè)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 去嘗試獲取對(duì)應(yīng)的 _YYModelPropertyMeta
2.2.3.2 如果有改_YYModelPropertyMeta,并且有setter 方法。
2.2.3.2.1 依據(jù)propertyMeta,
用objc_msgSend給對(duì)象發(fā)送 setter 方法。value是之前字典的 value。
接下來(lái)的是復(fù)雜版本的。更加全面!
1.二進(jìn)制Json -> OC Dictionary
2.OC Dictionary -> Model
2.1 通過(guò)運(yùn)行時(shí)獲取類的元信息(_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(此時(shí)比之前少了自定義的映射)
2.1.7.1 簡(jiǎn)單的依據(jù) name ,添加到 Mapper 中。
2.1.8 把大量局部變量賦值到全局變量。立一些 flag。
2.2 多態(tài):此處可以根據(jù)OC Dictionary更改當(dāng)前的類。一般在用來(lái)指向子類。大部分情況不用。
2.3 使用類的元信息發(fā)生字典轉(zhuǎn)模型
2.3.1 獲取之前生成的 _YYModelMeta
2.3.2 生成結(jié)構(gòu)體ModelSetContext context
2.3.3 如果 模型屬性數(shù)量 大于 字典內(nèi)數(shù)量(那個(gè)少遍歷哪個(gè))
2.3.3.1 遍歷 OC Dictionary,context傳參
2.3.3.1 以字典的 key 為 key 去嘗試獲取對(duì)應(yīng)的 _YYModelPropertyMeta
2.3.3.2 如果有改_YYModelPropertyMeta,并且有setter 方法。
2.3.3.2.1 依據(jù)propertyMeta,
用objc_msgSend給對(duì)象發(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)用,自己手動(dòng)完成一些值的賦值,類似自定義映射
??本篇文章只是梳理了字典轉(zhuǎn)模型的框架,細(xì)節(jié)可以在下面這個(gè)項(xiàng)目中查看。
??我給 YYModel 做了一定程度的注釋。會(huì)有一些細(xì)節(jié)的缺失,我會(huì)持續(xù)更新的。
??至于 Model轉(zhuǎn)字典和運(yùn)行時(shí)copy,nscoding之類的。難度不大,有空可能會(huì)更新。
??大家哪里不懂,問(wèn)問(wèn)我唄,也讓我看看哪個(gè)點(diǎn)沒(méi)講清楚。