[轉(zhuǎn)]iOS-三方庫-MJExtension源碼分析

原文鏈接:http://m.itdecent.cn/p/cc75458c5e6f

我們經(jīng)常需要從網(wǎng)絡(luò)上拉取json數(shù)據(jù),然后將json數(shù)據(jù)轉(zhuǎn)化為自己的模型數(shù)據(jù),將json數(shù)據(jù)轉(zhuǎn)化為我們自己的模型數(shù)據(jù)經(jīng)常使用的框架有YYModel和MJExtension,所以現(xiàn)在也是打算花一些時間看一下MJExtension的源碼,并且寫一篇博客記錄一下,因為不記錄下來的話感覺很容易忘,學(xué)習(xí)效果不佳。

一. MJExtension的使用

1. 最簡單的使用

模型:

//User.h@interfaceUser:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,copy)NSString*icon;@property(nonatomic,assign)unsignedintage;@property(nonatomic,copy)NSString*height;@property(nonatomic,strong)NSNumber*money;@end

字典轉(zhuǎn)模型:

//ViewController.mNSDictionary*dict=@{@"name":@"Jack",@"icon":@"lufy.png",@"age":@20,@"height":@"1.55",@"money":@100.9};// JSON -> UserUser*user=[User mj_objectWithKeyValues:dict];NSLog(@"name=%@, icon=%@, age=%u, height=%@, money=%@",user.name,user.icon,user.age,user.height,user.money);

打印結(jié)果:

name=Jack, icon=lufy.png, age=20, height=1.55, money=100.9

通過一句簡單的代碼,就把字典數(shù)據(jù)轉(zhuǎn)化為了模型數(shù)據(jù),非常方便簡潔。

2. 復(fù)雜一點的使用

很多時候json轉(zhuǎn)模型都不是這樣簡單,有時候會出現(xiàn)模型中嵌套模型或者模型中的屬性名和json數(shù)據(jù)中的key不一致的情況。

下面看一下一個Student類的模型:

//Student.h@interfaceStudent:NSObject@property(nonatomic,copy)NSString*ID;@property(nonatomic,copy)NSString*desc;@property(nonatomic,copy)NSString*nowName;@property(nonatomic,copy)NSString*oldName;@property(nonatomic,copy)NSString*nameChangedTime;@property(nonatomic,strong)Bag*bag;@end

我們看到Student模型中嵌套著Bag這個模型:

//Bag.h@interfaceBag:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)double*price;@end

然后我們再看一下json數(shù)據(jù):

NSDictionary*dict=@{@"id":@"20",@"description":@"kids",@"name":@{@"newName":@"lufy",@"oldName":@"kitty",@"info":@[@"test-data",@{@"nameChangedTime":@"2013-08"}]},@"other":@{@"bag":@{@"name":@"a red bag",@"price":@100.7}}};

可以看到字典數(shù)據(jù)中是id,而模型中是ID,同樣也有desc和description,模型中有newName和oldName這些屬性,而字典中這些屬性在name字段下面,而且nameChangedTime層級也不一樣,bag屬性也是一樣的道理,那么怎么辦呢?

我們只需要讓模型遵守MJKeyValue協(xié)議,實現(xiàn)+ (NSDictionary *)mj_replacedKeyFromPropertyName;協(xié)議方法,如下:

//Student.m/**

*? 將屬性名換為其他key去字典中取值

*

*? @return 字典中的key是屬性名,value是從字典中取值用的key

*/+(NSDictionary*)mj_replacedKeyFromPropertyName{return@{@"ID":@"id",@"desc":@"description",@"oldName":@"name.oldName",@"nowName":@"name.newName",@"nameChangedTime":@"name.info[1].nameChangedTime",@"bag":@"other.bag"};}

這個方法的作用就是在給模型賦值的時候,把右邊字段的值賦給模型中左邊字段的屬性

轉(zhuǎn)化一下試試:

// JSON -> StudentStudent*stu=[Student mj_objectWithKeyValues:dict];// PrintingNSLog(@"ID=%@, desc=%@, oldName=%@, nowName=%@, nameChangedTime=%@",stu.ID,stu.desc,stu.oldName,stu.nowName,stu.nameChangedTime);// ID=20, desc=kids, oldName=kitty, nowName=lufy, nameChangedTime=2013-08NSLog(@"bagName=%@, bagPrice=%d",stu.bag.name,stu.bag.price);// bagName=a red bag, bagPrice=100.700000

這里需要注意一個地方就是模型中的nameChangedTime這個屬性,在字典中去取值的時候是取name.info[1].nameChangedTime這個字段的值,這個在后面我們講核心源碼的時候會用到。后面講源碼也會以上面這個為例子來講,這樣比較好理解。

二. MJExtension核心類簡介

1. MJFoundation

//判斷一個類是否是foundation類及其子類+(BOOL)isClassFromFoundation:(Class)c;//判斷屬性是否是協(xié)議里面定義的+(BOOL)isFromNSObjectProtocolProperty:(NSString*)propertyName;

2. MJProperty

這個類非常重要,這個類是對我們類中屬性的再封裝。

首先會通過runtime的方法去遍歷類中的屬性:

unsignedintcount;objc_property_t*propertyList=class_copyPropertyList([Studentclass],&count);for(inti=0;i<count;i++){objc_property_t property=propertyList[i];constchar*propertyName=property_getName(property);constchar*attris=property_getAttributes(property);NSLog(@"%s %s",propertyName,attris);}free(propertyList);

打印結(jié)果:

IDT@"NSString",C,N,V_IDdescT@"NSString",C,N,V_descnowNameT@"NSString",C,N,V_nowNameoldNameT@"NSString",C,N,V_oldNamenameChangedTimeT@"NSString",C,N,V_nameChangedTimebagT@"Bag",&,N,V_bag

通過char類型的attris字符串我們可以看到,它中間有一個串是表示它是屬于哪一個類的,比如NSString,Bag。

通過遍歷類的屬性,我們得到了objc_property_t類型的屬性對象,然后使用這個objc_property_t對象來創(chuàng)建一個對應(yīng)的MJProperty對象,我們看看MJ大神是怎么做的:

#pragmamark - 緩存+(instancetype)cachedPropertyWithProperty:(objc_property_t)property{//這里的self是MJProperty類對象MJProperty*propertyObj=objc_getAssociatedObject(self,property);if(propertyObj==nil){propertyObj=[[selfalloc]init];propertyObj.property=property;//給MJProperty類對象添加關(guān)聯(lián)對象,使用property作為key,關(guān)聯(lián)propertyObj實例對象objc_setAssociatedObject(self,property,propertyObj,OBJC_ASSOCIATION_RETAIN_NONATOMIC);}returnpropertyObj;}

首先MJ大神通過objc_property_t對象這個key去緩存中取,如果緩存中取不到,那么就根據(jù)objc_property_t來創(chuàng)建一個MJProperty對象,并且把這個MJProperty對象通過property這個key與MJProperty類對象關(guān)聯(lián)起來。那么下次如果再從緩存中取同一個objc_property_t對應(yīng)的MJProperty對象就能取到了,就不用再創(chuàng)建了。這也是MJ大神使用緩存的一個地方。

上面代碼塊中propertyObj.property = property;這行代碼觸發(fā)了MJProperty對象的setter方法:

設(shè)置屬性.png

MJProperty有一個type屬性,這個屬性是MJPropertyType類的,就是表示MJProperty對象的property屬性是屬于什么類型的,具體可看源碼。

另外每一個MJProperty對象還持有著兩個字典,一個是propertyKeysDict,一個是objectClassInArrayDict。

propertyKeysDict

這個字典的key是NSStringFromClass(class),值是一個數(shù)組,比如在復(fù)雜一點的應(yīng)用中,給模型中的nameChangedTime這個屬性賦值的時候,在字典中去取值的時候要對應(yīng)name.info[1].nameChangedTime這個字段的值。那么就要把name,info,1,nameChangedTim,這個四個字段分別封裝為一個MJPropertyKey,加入一個數(shù)組中,作為value。這個數(shù)組在最終取值的時候會用到。

objectClassInArrayDict

這個字典的key也是NSStringFromClass(class),值是一個類對象,表示如果這個MJProperty對象的類型是數(shù)組,并且數(shù)組中的元素類型是模型,那么這個字典的value就是模型的類對象。

上面兩個字典的解釋如果沒看懂也沒關(guān)系,接著往下看,看完核心源碼分析就懂了。

3. MJPropertyKey

上面說過,給模型中的nameChangedTime這個屬性賦值的時候,在字典中取值的時候要對應(yīng)name.info[1].nameChangedTime這個字段的值,那么就要把name,info,1,nameCHangedTime這四個字段分別封裝成一個MJPropertyKey。

它有兩個屬性,一個屬性是name,也就是name、info、1這種,還有一個屬性就是type,它是自定義的MJPropertyKeyType類型的枚舉值,這個枚舉值有兩種類型,即MJPropertyKeyTypeDictionary和MJPropertyKeyTypeArray,像name、info這種就屬于MJPropertyKeyTypeDictionary類型的,1就屬于MJPropertyKeyTypeArray類型的。這個也是在取值的時候用的,類型是MJPropertyKeyTypeDictionary就是從字典中取值,類型是MJPropertyKeyTypeArray就是從數(shù)組中取值。

4. MJPropertyType

MJProperty類有一個屬性是type,這個屬性是MJPropertyType類的,這個type屬性就是表征這個MJProperty對象它的property屬性屬于什么類,NSString類或者NSNumber類等等。MJProperty對象的type是通過截取property的attributes得到code然后初始化為MJPropertyType對象得到的:

_type=[MJPropertyType cachedTypeWithCode:code];

三. 核心源碼分析

我們就從復(fù)雜一點的使用這個例子去看一下MJExtension的核心源碼。

沿著+ (instancetype)mj_objectWithKeyValues:(id)keyValues;這個方法一直往下查找就能找到其核心代碼:

/**

核心代碼:

*/-(instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext*)context{// 獲得JSON對象keyValues=[keyValues mj_JSONObject];MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]],self,[selfclass],@"keyValues參數(shù)不是一個字典");//類對象Class clazz=[selfclass];//白名單NSArray*allowedPropertyNames=[clazz mj_totalAllowedPropertyNames];//黑名單NSArray*ignoredPropertyNames=[clazz mj_totalIgnoredPropertyNames];//通過封裝的方法回調(diào)一個通過運行時編寫的,用于返回屬性列表的方法。[clazz mj_enumerateProperties:^(MJProperty*property,BOOL*stop){@try{// 0.檢測是否被忽略if(allowedPropertyNames.count&&![allowedPropertyNames containsObject:property.name])return;if([ignoredPropertyNames containsObject:property.name])return;// 1.取出屬性值id value;NSArray*propertyKeyses=[property propertyKeysForClass:clazz];for(NSArray*propertyKeysinpropertyKeyses){value=keyValues;for(MJPropertyKey*propertyKeyinpropertyKeys){value=[propertyKey valueInObject:value];}if(value)break;}// 值的過濾id newValue=[clazz mj_getNewValueFromObject:selfoldValue:value property:property];if(newValue!=value){// 有過濾后的新值[property setValue:newValue forObject:self];return;}// 如果沒有值,就直接返回if(!value||value==[NSNull null])return;// 2.復(fù)雜處理MJPropertyType*type=property.type;Class propertyClass=type.typeClass;Class objectClass=[property objectClassInArrayForClass:[selfclass]];// 不可變 -> 可變處理if(propertyClass==[NSMutableArray class]&&[value isKindOfClass:[NSArray class]]){value=[NSMutableArray arrayWithArray:value];}elseif(propertyClass==[NSMutableDictionary class]&&[value isKindOfClass:[NSDictionary class]]){value=[NSMutableDictionary dictionaryWithDictionary:value];}elseif(propertyClass==[NSMutableString class]&&[value isKindOfClass:[NSString class]]){value=[NSMutableString stringWithString:value];}elseif(propertyClass==[NSMutableData class]&&[value isKindOfClass:[NSData class]]){value=[NSMutableData dataWithData:value];}if(!type.isFromFoundation&&propertyClass){// 模型屬性value=[propertyClass mj_objectWithKeyValues:value context:context];}elseif(objectClass){if(objectClass==[NSURL class]&&[value isKindOfClass:[NSArray class]]){// string array -> url arrayNSMutableArray*urlArray=[NSMutableArray array];for(NSString*stringinvalue){if(![string isKindOfClass:[NSString class]])continue;[urlArray addObject:string.mj_url];}value=urlArray;}else{// 字典數(shù)組-->模型數(shù)組value=[objectClass mj_objectArrayWithKeyValuesArray:value context:context];}}else{if(propertyClass==[NSString class]){if([value isKindOfClass:[NSNumber class]]){// NSNumber -> NSStringvalue=[value description];}elseif([value isKindOfClass:[NSURL class]]){// NSURL -> NSStringvalue=[value absoluteString];}}elseif([value isKindOfClass:[NSString class]]){if(propertyClass==[NSURL class]){// NSString -> NSURL// 字符串轉(zhuǎn)碼value=[value mj_url];}elseif(type.isNumberType){NSString*oldValue=value;// NSString -> NSNumberif(type.typeClass==[NSDecimalNumber class]){value=[NSDecimalNumber decimalNumberWithString:oldValue];}else{NSDecimalNumber*decimalValue=[NSDecimalNumber decimalNumberWithString:oldValue];value=decimalValue==[NSDecimalNumber notANumber]?@(0):@(decimalValue.doubleValue);}// 如果是BOOLif(type.isBoolType){// 字符串轉(zhuǎn)BOOL(字符串沒有charValue方法)// 系統(tǒng)會調(diào)用字符串的charValue轉(zhuǎn)為BOOL類型NSString*lower=[oldValue lowercaseString];if([lower isEqualToString:@"yes"]||[lower isEqualToString:@"true"]){value=@YES;}elseif([lower isEqualToString:@"no"]||[lower isEqualToString:@"false"]){value=@NO;}}}}elseif([value isKindOfClass:[NSNumber class]]&&propertyClass==[NSDecimalNumber class]){// 過濾 NSDecimalNumber類型if(![value isKindOfClass:[NSDecimalNumber class]]){value=[NSDecimalNumber decimalNumberWithDecimal:[((NSNumber*)value)decimalValue]];}}// value和property類型不匹配if(propertyClass&&![value isKindOfClass:propertyClass]){value=nil;}}// 3.賦值[property setValue:value forObject:self];}@catch(NSException*exception){MJExtensionBuildError([selfclass],exception.reason);MJExtensionLog(@"%@",exception);}}];// 轉(zhuǎn)換完畢if([selfrespondsToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]){[selfmj_didConvertToObjectWithKeyValues:keyValues];}#pragmaclang diagnostic push#pragmaclang diagnostic ignored"-Wdeprecated-declarations"if([selfrespondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]){[selfmj_keyValuesDidFinishConvertingToObject];}if([selfrespondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]){[selfmj_keyValuesDidFinishConvertingToObject:keyValues];}#pragmaclang diagnostic popreturnself;}

這一部分代碼很長,我們一點一點來看:

1. 將json數(shù)據(jù)轉(zhuǎn)化為foundation類型

// 獲得JSON對象keyValues=[keyValues mj_JSONObject];MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]],self,[selfclass],@"keyValues參數(shù)不是一個字典");

mj_JSONObject方法:

-(id)mj_JSONObject{if([selfisKindOfClass:[NSString class]]){return[NSJSONSerialization JSONObjectWithData:[((NSString*)self)dataUsingEncoding:NSUTF8StringEncoding]options:kNilOptions error:nil];}elseif([selfisKindOfClass:[NSData class]]){return[NSJSONSerialization JSONObjectWithData:(NSData*)selfoptions:kNilOptions error:nil];}returnself.mj_keyValues;}

2. 獲取白名單和黑名單

//類對象Class clazz=[selfclass];//白名單NSArray*allowedPropertyNames=[clazz mj_totalAllowedPropertyNames];//黑名單NSArray*ignoredPropertyNames=[clazz mj_totalIgnoredPropertyNames];

allowedPropertyNames是允許進行字典和模型轉(zhuǎn)換的屬性名數(shù)組,ignoredPropertyNames是不允許進行字典和模型轉(zhuǎn)換額屬性名數(shù)組,要求自己的模型類遵守MJKeyValue協(xié)議,實現(xiàn)協(xié)議的如下方法:

/**

*? 只有這個數(shù)組中的屬性名才允許進行字典和模型的轉(zhuǎn)換

*/+(NSArray*)mj_allowedPropertyNames;/**

*? 這個數(shù)組中的屬性名將會被忽略:不進行字典和模型的轉(zhuǎn)換

*/+(NSArray*)mj_ignoredPropertyNames;

然后這倆數(shù)組就是在上面協(xié)議方法中獲取的。

3. 遍歷整個類的屬性

//通過封裝的方法回調(diào)一個通過運行時編寫的,用于返回屬性列表的方法+(void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration{// 獲得成員變量MJExtensionSemaphoreCreate? ? MJExtensionSemaphoreWait? ? NSArray*cachedProperties=[selfmj_properties];MJExtensionSemaphoreSignal// 遍歷成員變量BOOL stop=NO;for(MJProperty*propertyincachedProperties){//回調(diào)blockenumeration(property,&stop);if(stop)break;}}

上面代碼,先獲取類中所有的屬性,然后使用for循環(huán)遍歷每一個屬性,然后執(zhí)行回調(diào)block。

先看一下+ (NSMutableArray *)properties;方法,代碼如下:

+(NSMutableArray*)mj_properties{//先從緩存中獲取成員變量NSMutableArray*cachedProperties=[selfmj_propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];if(cachedProperties==nil){//緩存中沒有就遍歷類中的每一個成員變量if(cachedProperties==nil){cachedProperties=[NSMutableArray array];[selfmj_enumerateClasses:^(__unsafe_unretained Class c,BOOL*stop){// 1.獲得所有的成員變量unsignedintoutCount=0;//遍歷類中的屬性objc_property_t*properties=class_copyPropertyList(c,&outCount);// 2.遍歷每一個成員變量for(unsignedinti=0;i<outCount;i++){//遍歷,然后根據(jù)OC的屬性創(chuàng)建MJProperty//給MJProperty類對象添加關(guān)聯(lián)對象,objc_propert作為key,MJProperty實例對象作為關(guān)聯(lián)的對象MJProperty*property=[MJProperty cachedPropertyWithProperty:properties[i]];// 過濾掉Foundation框架類里面的屬性if([MJFoundation isClassFromFoundation:property.srcClass])continue;// 過濾掉`hash`, `superclass`, `description`, `debugDescription`if([MJFoundation isFromNSObjectProtocolProperty:property.name])continue;property.srcClass=c;//很重要的兩個方法[property setOriginKey:[selfmj_propertyKey:property.name]forClass:self];[property setObjectClassInArray:[selfmj_propertyObjectClassInArray:property.name]forClass:self];[cachedProperties addObject:property];}// 3.釋放內(nèi)存free(properties);}];//將獲取的cachedProperties緩存下來[selfmj_propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)]=cachedProperties;}}returncachedProperties;}

上面代碼,先從緩存中獲取屬性列表,如果緩存中沒有,就遍歷類中的每一個屬性,拿到屬性列表之后再存到緩存。核心代碼就是獲取類中的所有成員變量,然后包裝成MJProperty對象,如下:

[selfmj_enumerateClasses:^(__unsafe_unretained Class c,BOOL*stop){// 1.獲得所有的成員變量unsignedintoutCount=0;//遍歷類中的屬性objc_property_t*properties=class_copyPropertyList(c,&outCount);// 2.遍歷每一個成員變量for(unsignedinti=0;i<outCount;i++){//遍歷,然后根據(jù)OC的屬性創(chuàng)建MJProperty//給MJProperty類對象添加關(guān)聯(lián)對象,objc_propert作為key,MJProperty實例對象作為關(guān)聯(lián)的對象MJProperty*property=[MJProperty cachedPropertyWithProperty:properties[i]];// 過濾掉Foundation框架類里面的屬性if([MJFoundation isClassFromFoundation:property.srcClass])continue;// 過濾掉`hash`, `superclass`, `description`, `debugDescription`if([MJFoundation isFromNSObjectProtocolProperty:property.name])continue;property.srcClass=c;//很重要的兩個方法[property setOriginKey:[selfmj_propertyKey:property.name]forClass:self];[property setObjectClassInArray:[selfmj_propertyObjectClassInArray:property.name]forClass:self];[cachedProperties addObject:property];}// 3.釋放內(nèi)存free(properties);}];

首先通過+ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration這個方法去遍歷當(dāng)前模型類及其父類,當(dāng)追溯到Foundation類型的類時就停止遍歷,方法實現(xiàn)如下:

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

比如有一個Person類,其有兩個屬性name和sex,有一個Student類是繼承自Person類的,這個Student類自己有一個school屬性。那么當(dāng)我們使用runtime的方法讀取Student類的屬性列表時,只能讀取到一個自己聲明的屬性school。但是實際上name和sex也是它的屬性,所以這個時候就要遍歷其父類,拿到所有的屬性。

當(dāng)我們拿到模型類的objc_property_t類型的屬性時,就通過+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property方法將其封裝成MJProperty對象:

+(instancetype)cachedPropertyWithProperty:(objc_property_t)property{//這里的self是MJProperty類對象MJProperty*propertyObj=objc_getAssociatedObject(self,property);if(propertyObj==nil){propertyObj=[[selfalloc]init];propertyObj.property=property;//給MJProperty類對象添加關(guān)聯(lián)對象,使用property作為key,關(guān)聯(lián)propertyObj實例對象objc_setAssociatedObject(self,property,propertyObj,OBJC_ASSOCIATION_RETAIN_NONATOMIC);}returnpropertyObj;}

如上,這個方法先嘗試從關(guān)聯(lián)屬性中通過property對象這個key來取出MJProperty對象,如果取不到就創(chuàng)建一個MJProperty對象,并通過property這個key將其與MJProperty的類對象關(guān)聯(lián)起來,這樣下次就可以直接通過關(guān)聯(lián)屬性來得到MJProperty的值了。然后再通過propertyObj.property = property;這行代碼觸發(fā)set方法,在set方法里面為MJProperty對象的name屬性和type屬性賦值,其中type屬性就是和MJProperty對象關(guān)聯(lián)的property屬于什么類,是NSNumber類還是BOOL類等等:

-(void)setProperty:(objc_property_t)property{_property=property;MJExtensionAssertParamNotNil(property);// 1.屬性名_name=@(property_getName(property));// 2.成員類型NSString*attrs=@(property_getAttributes(property));NSUInteger dotLoc=[attrs rangeOfString:@","].location;NSString*code=nil;NSUInteger loc=1;if(dotLoc==NSNotFound){// 沒有,code=[attrs substringFromIndex:loc];}else{code=[attrs substringWithRange:NSMakeRange(loc,dotLoc-loc)];}_type=[MJPropertyType cachedTypeWithCode:code];}

下面兩行代碼非常重要

[propertysetOriginKey:[selfmj_propertyKey:property.name]forClass:self];[propertysetObjectClassInArray:[selfmj_propertyObjectClassInArray:property.name]forClass:self];

對于第一行代碼

+ (id)propertyKey:(NSString *)propertyName這個方法是獲取模型的屬性名在字典中對應(yīng)的key,什么意思呢?還是拿第二個例子來說,它有一個nameChangedTime屬性,由于我們在模型類中實現(xiàn)了+ (NSDictionary *)mj_replacedKeyFromPropertyName這個方法,且這個方法中與nameChangedTime相對應(yīng)的是name.info[1].nameChangedTime,所以+ (id)propertyKey:(NSString *)propertyName返回的就是name.info[1].nameChangedTime這個字符串。

對于- (void)setOriginKey:(id)originKey forClass:(Class)c方法,這個方法會把name.info[1].nameChangedTime這個字符串拆解成一段一段,并封裝成一個個MJPropertyKey對象,組成數(shù)組,賦值給MJProperty的propertyKeysDict這個字典:

setOriginKey forClass.png

- (void)setPorpertyKeys:(NSArray *)propertyKeys forClass:(Class)c方法的實現(xiàn)如下:

/** 對應(yīng)著字典中的多級key */-(void)setPorpertyKeys:(NSArray*)propertyKeys forClass:(Class)c{if(propertyKeys.count==0)return;NSString*key=NSStringFromClass(c);if(!key)return;MJ_LOCK(self.propertyKeysLock);self.propertyKeysDict[key]=propertyKeys;MJ_UNLOCK(self.propertyKeysLock);}

對于第二行代碼

如果模型中有數(shù)組類型的屬性,并且數(shù)組中的元素也是模型類,那么就需要模型類遵守MJKeyValue協(xié)議,實現(xiàn)+ (NSDictionary *)mj_objectClassInArray;協(xié)議方法,就像這樣:模型類中有一個數(shù)組類型的屬性statuses,數(shù)組中的元素類型是模型,模型類是Status;另一個數(shù)組類型的屬性是ads,數(shù)組中的元素類型是模型,模型類是Ad,如下:

+(NSDictionary*)mj_objectClassInArray{return@{@"statuses":@"Status",@"ads":@"Ad"};}

這時如果在+ (Class)propertyObjectClassInArray:(NSString *)propertyName方法中傳入statuses屬性,那么返回的就是Status類,如下:

propertyObjectClassInArray.png

然后- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c方法將這個Status類對象賦值給MJProperty對象的objectClassInArrayDict字典。

最后再通過+ (NSMutableDictionary *)mj_propertyDictForKey:(const void *)key方法將獲取的cachedProperties緩存下來,如下:

//將獲取的cachedProperties緩存下來[selfmj_propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)]=cachedProperties;

方法實現(xiàn)如下:

+(NSMutableDictionary*)mj_propertyDictForKey:(constvoid*)key{//靜態(tài)變量staticNSMutableDictionary*replacedKeyFromPropertyNameDict;staticNSMutableDictionary*replacedKeyFromPropertyName121Dict;staticNSMutableDictionary*newValueFromOldValueDict;staticNSMutableDictionary*objectClassInArrayDict;staticNSMutableDictionary*cachedPropertiesDict;staticdispatch_once_t onceToken;dispatch_once(&onceToken,^{replacedKeyFromPropertyNameDict=[NSMutableDictionary dictionary];replacedKeyFromPropertyName121Dict=[NSMutableDictionary dictionary];newValueFromOldValueDict=[NSMutableDictionary dictionary];objectClassInArrayDict=[NSMutableDictionary dictionary];cachedPropertiesDict=[NSMutableDictionary dictionary];});if(key==&MJReplacedKeyFromPropertyNameKey)returnreplacedKeyFromPropertyNameDict;if(key==&MJReplacedKeyFromPropertyName121Key)returnreplacedKeyFromPropertyName121Dict;if(key==&MJNewValueFromOldValueKey)returnnewValueFromOldValueDict;if(key==&MJObjectClassInArrayKey)returnobjectClassInArrayDict;if(key==&MJCachedPropertiesKey)returncachedPropertiesDict;returnnil;}

上面static的靜態(tài)字典,保證程序運行中,字典會一直存在內(nèi)存中,從而達到緩存的目的。

到這里遍歷類的所有屬性就結(jié)束了,這樣獲得了整個類的所有屬性,每個屬性被封裝成了一個MJProperty對象,MJProperty對象有一個property屬性,還有type屬性來表征這個屬性屬于什么類。此外MJProperty對象還保存著兩個字典propertyKeysDict和objectClassInArrayDict,這兩個字典的key都是NSStringFromClass(c),前者的value是一個數(shù)組,這個數(shù)組里面的元素是MJPropertyKey類型的,主要是用來取值用的,后者的value是一個類對象,如果屬性是一個數(shù)組類型的屬性,且數(shù)組元素是模型類型,那么這個值就是模型的類對象。

4. 對模型進行賦值

對模型進行賦值是在+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration方法的回調(diào)block里一個一個進行的,我們一行一行的看回調(diào)block。

① 檢測是否被忽略

如果這個屬性不在屬性白名單里或者在屬性黑名單里,那么就返回,不對屬性賦值:

// 0.檢測是否被忽略if(allowedPropertyNames.count&&![allowedPropertyNames containsObject:property.name])return;if([ignoredPropertyNames containsObject:property.name])return;

② 取出屬性值

從每個屬性的propertyKeysDict字典中取出propertyKeys數(shù)組,根據(jù)propertyKeys數(shù)組來取值:

// 1.取出屬性值idvalue;NSArray*propertyKeyses=[property propertyKeysForClass:clazz];for(NSArray*propertyKeysinpropertyKeyses){value=keyValues;for(MJPropertyKey*propertyKeyinpropertyKeys){value=[propertyKey valueInObject:value];}if(value)break;}

我們看一下- (id)valueInObject:(id)object這個方法是怎么操作的:

valueInObject.png

③ 值的過濾

// 值的過濾id newValue=[clazz mj_getNewValueFromObject:self oldValue:valueproperty:property];if(newValue!=value){// 有過濾后的新值[property setValue:newValue forObject:self];return;}// 如果沒有值,就直接返回if(!value||value==[NSNullnull])return;

想要明白上面的代碼就要明白MJKeyValue協(xié)議的如下方法:

/**

*? 舊值換新值,用于過濾字典中的值

*

*? @param oldValue 舊值

*

*? @return 新值

*/-(id)mj_newValueFromOldValue:(id)oldValueproperty:(MJProperty *)property;

MJExtension官方的實例如下:NSString -> NSDate, nil -> @""【過濾字典的值(比如字符串日期處理為NSDate、字符串nil處理為@"")】

// Book#import"MJExtension.h"@implementationBook-(id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty*)property{if([property.name isEqualToString:@"publisher"]){if(oldValue==nil)return@"";}elseif(property.type.typeClass==[NSDate class]){NSDateFormatter*fmt=[[NSDateFormatter alloc]init];fmt.dateFormat=@"yyyy-MM-dd";return[fmt dateFromString:oldValue];}returnoldValue;}@end// NSDictionaryNSDictionary*dict=@{@"name":@"5分鐘突破iOS開發(fā)",@"publishedTime":@"2011-09-10"};// NSDictionary -> BookBook*book=[Book mj_objectWithKeyValues:dict];// printingNSLog(@"name=%@, publisher=%@, publishedTime=%@",book.name,book.publisher,book.publishedTime);

比如,我們需要將服務(wù)端返回的字符串日期處理為NSDate、字符串nil處理為@"",就按照上面的邏輯處理就好了。現(xiàn)在你應(yīng)該就明白值的過濾是做什么的了。

④ 不可變 -> 可變處理

如果屬性的類型是可變的類型,而取出的value是不可變的類型,那么就要把不可變類型變換為可變的類型:

// 2.復(fù)雜處理MJPropertyType*type=property.type;Class propertyClass=type.typeClass;Class objectClass=[property objectClassInArrayForClass:[selfclass]];// 不可變 -> 可變處理if(propertyClass==[NSMutableArray class]&&[value isKindOfClass:[NSArray class]]){value=[NSMutableArray arrayWithArray:value];}elseif(propertyClass==[NSMutableDictionary class]&&[value isKindOfClass:[NSDictionary class]]){value=[NSMutableDictionary dictionaryWithDictionary:value];}elseif(propertyClass==[NSMutableString class]&&[value isKindOfClass:[NSString class]]){value=[NSMutableString stringWithString:value];}elseif(propertyClass==[NSMutableData class]&&[value isKindOfClass:[NSData class]]){value=[NSMutableData dataWithData:value];}

⑤ 復(fù)雜處理

上面就是完成了對屬性的第一步賦值,但是這還不夠,如果這個屬性是模型類型,那么還要對這個模型再進行一次字典轉(zhuǎn)模型操作。如果這個屬性是數(shù)組類型且數(shù)組元素是模型類型,那么還要進行字典數(shù)組轉(zhuǎn)模型數(shù)組的操作?;蛘邔傩允荖SURL類型,value是NSString類型,這樣也要進行一下轉(zhuǎn)換:

if(!type.isFromFoundation&&propertyClass){// 模型屬性//由于屬性是模型類型,所以繼續(xù)進行字典轉(zhuǎn)模型操作value=[propertyClass mj_objectWithKeyValues:value context:context];}elseif(objectClass){if(objectClass==[NSURL class]&&[value isKindOfClass:[NSArray class]]){// string array -> url arrayNSMutableArray*urlArray=[NSMutableArray array];for(NSString*stringinvalue){if(![string isKindOfClass:[NSString class]])continue;[urlArray addObject:string.mj_url];}value=urlArray;}else{// 字典數(shù)組-->模型數(shù)組//屬性是數(shù)組類型且數(shù)組元素是模型,則進行字典數(shù)組轉(zhuǎn)模型數(shù)組的操作value=[objectClass mj_objectArrayWithKeyValuesArray:value context:context];}}else{if(propertyClass==[NSString class]){if([value isKindOfClass:[NSNumber class]]){// NSNumber 轉(zhuǎn) NSStringvalue=[value description];}elseif([value isKindOfClass:[NSURL class]]){// NSURL -> NSStringvalue=[value absoluteString];}}elseif([value isKindOfClass:[NSString class]]){if(propertyClass==[NSURL class]){// NSString -> NSURL// 字符串轉(zhuǎn)碼value=[value mj_url];}elseif(type.isNumberType){NSString*oldValue=value;// NSString -> NSNumberif(type.typeClass==[NSDecimalNumber class]){value=[NSDecimalNumber decimalNumberWithString:oldValue];}else{NSDecimalNumber*decimalValue=[NSDecimalNumber decimalNumberWithString:oldValue];value=decimalValue==[NSDecimalNumber notANumber]?@(0):@(decimalValue.doubleValue);}// 如果是BOOLif(type.isBoolType){// 字符串轉(zhuǎn)BOOL(字符串沒有charValue方法)// 系統(tǒng)會調(diào)用字符串的charValue轉(zhuǎn)為BOOL類型NSString*lower=[oldValue lowercaseString];if([lower isEqualToString:@"yes"]||[lower isEqualToString:@"true"]){value=@YES;}elseif([lower isEqualToString:@"no"]||[lower isEqualToString:@"false"]){value=@NO;}}}}elseif([value isKindOfClass:[NSNumber class]]&&propertyClass==[NSDecimalNumber class]){// 過濾 NSDecimalNumber類型if(![value isKindOfClass:[NSDecimalNumber class]]){value=[NSDecimalNumber decimalNumberWithDecimal:[((NSNumber*)value)decimalValue]];}}// value和property類型不匹配if(propertyClass&&![value isKindOfClass:propertyClass]){value=nil;}}

⑥ 賦值

// 3.賦值[property setValue:valueforObject:self];

使用KVC賦值,如下:

/**

*? 設(shè)置成員變量的值

*/-(void)setValue:(id)value forObject:(id)object{if(self.type.KVCDisabled||value==nil)return;[object setValue:value forKey:self.name];}

⑦ 轉(zhuǎn)換完畢

轉(zhuǎn)換完畢,回調(diào)MJKeyValue協(xié)議方法,返回self,如下:

// 轉(zhuǎn)換完畢if([selfrespondsToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]){[selfmj_didConvertToObjectWithKeyValues:keyValues];}#pragmaclang diagnostic push#pragmaclang diagnostic ignored"-Wdeprecated-declarations"if([selfrespondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]){[selfmj_keyValuesDidFinishConvertingToObject];}if([selfrespondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]){[selfmj_keyValuesDidFinishConvertingToObject:keyValues];}#pragmaclang diagnostic popreturnself;

這里,整個模型賦值的過程也就完成了。

四. MJExtension中的一些緩存操作

MJExtension中進行了大量的緩存操作來優(yōu)化性能,下面講幾個比較重要的緩存,理解了這些緩存也有助于更深入的理解整個框架。

NSObject+MJProperty這個分類中保存著一個字典cachedPropertiesDict,這個字典的key是NSStringFromClass(class),值就是一個數(shù)組,這個數(shù)組里面存放著一個類的所有屬性。這樣當(dāng)我們下一次還要對同一個類進行模型賦值操作,就可以直接從這個字典里面取出這個類的一個包含所有屬性的數(shù)組了。

MJProperty這個類中,通過runtime的動態(tài)關(guān)聯(lián)屬性的方法,關(guān)聯(lián)每一個objc_property_t,注意是與類對象相關(guān)聯(lián)。value是MJProperty對象:

+(instancetype)cachedPropertyWithProperty:(objc_property_t)property{//這里的self是MJProperty類對象MJProperty*propertyObj=objc_getAssociatedObject(self,property);if(propertyObj==nil){propertyObj=[[selfalloc]init];propertyObj.property=property;//給MJProperty類對象添加關(guān)聯(lián)對象,使用property作為key,關(guān)聯(lián)propertyObj實例對象objc_setAssociatedObject(self,property,propertyObj,OBJC_ASSOCIATION_RETAIN_NONATOMIC);}returnpropertyObj;}

想象一種情況,Teacher和Student都繼承自Person,所以Teacher和Student都有Person的屬性,當(dāng)我們先給Teacher模型賦值的時候,Person類的每一個屬性已經(jīng)調(diào)用了上面的代碼塊封裝成了MJProperty對象,并與MJProperty類對象相關(guān)聯(lián)。那么當(dāng)我們再給Student模型賦值的時候,也會遍歷Person類的屬性,但是這個時候通過MJProperty *propertyObj = objc_getAssociatedObject(self, property);已經(jīng)能得到MJProperty對象了,不用去創(chuàng)建。

在MJPropertyType中有一個types字典,這個字典是在單例中初始化的,types字典的key是code,value是MJPropertyType對象,每次有新的code,就添加到這個字典里面去,這樣的好處就是如果code一致,就可以直接從字典中取MJPropertyType。

每一個MJProperty對象都有一個propertyKeysDict字典,這個字典的key是NSStringFromClass(class),值是一個數(shù)組,比如一個MJProperty的名字是name.info[1].text,那么這個數(shù)組就會包括4個MJPropertyKey對象,分別表示name,info,1,text,這些key是在取值的時候用的。那么問題來了,為什么要設(shè)計字典來存儲呢 ,直接用一個數(shù)組來存儲不就好了嗎?

其實這個問題和2相似,因為我們在第二次遍歷Person類中的屬性的時候不用去創(chuàng)建一個MJProperty對象,直接通過關(guān)聯(lián)屬性去取值就好了,但是Student模型和Teacher模型它們的propertyKeys是有可能不一樣的,所以這里需要一個key來加以區(qū)分。

筆記地址:MJExtension筆記

原文地址:MJExtension源碼解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們經(jīng)常需要從網(wǎng)絡(luò)上拉取json數(shù)據(jù),然后將json數(shù)據(jù)轉(zhuǎn)化為自己的模型數(shù)據(jù),將json數(shù)據(jù)轉(zhuǎn)化為我們自己的模型數(shù)...
    Imkata閱讀 1,296評論 0 2
  • 在日常的iOS開發(fā)中,總會進行數(shù)據(jù)的轉(zhuǎn)換,比如請求服務(wù)端獲取數(shù)據(jù),解析數(shù)據(jù),轉(zhuǎn)換成對應(yīng)的model,這個轉(zhuǎn)換過...
    繁星mind閱讀 14,186評論 7 49
  • MJExtension A fast, convenient and nonintrusive conversio...
    雪山飛狐_91ae閱讀 1,660評論 0 13
  • MJExtension是json轉(zhuǎn)模型相當(dāng)便捷的一個三方庫。本本為一窺其內(nèi)部奧妙,文中肯定有不足之處,敬請指正。 ...
    roast_duck閱讀 884評論 0 1
  • MJExtension概述 MJExtension是是一個非常易用且功能強大的第三方Model和JSON相互轉(zhuǎn)化的...
    yuguang1閱讀 2,199評論 0 2

友情鏈接更多精彩內(nèi)容