繼續(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è)過程:
- 初步了解MJExtension的原理,并通過第一版的代碼學(xué)習(xí)基本的邏輯和思路沼沈。
- 閱讀學(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)化和封裝患膛。