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組成的字典
至于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)沒講清楚蒜埋。