跟着MJExtension实现简单的字典转模型框架 - 简书
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
演示代碼地址:https://github.com/codeWillwillCode/LearnMJExtension
最簡單的字典
首先,從最簡單的字典開始.
NSDictionary?*dict?=?@{???????????????????????????@"name"?:?@"Jack",???????????????????????????@"icon"?:?@"lufy.png",???????????????????????????@"age"?:?@"20",???????????????????????????@"height"?:?@1.55,???????????????????????????@"money"?:?@"100.9",???????????????????????????@"sex"?:?@(SexFemale),???????????????????????????@"gay"?:?@"1"}目標是拿到字典里的值(value)對User模型進行賦值.模型的屬性名對應(yīng)字典的鍵(key).
typedef?enum?{SexMale,SexFemale }?Sex;@interface?User?:?NSObject/**?名稱?*/@property?(copy,?nonatomic)?NSString?*name;/**?頭像?*/@property?(copy,?nonatomic)?NSString?*icon;/**?年齡?*/@property?(assign,?nonatomic)?unsigned?int?age;/**?身高?*/@property?(copy,?nonatomic)?NSString?*height;/**?財富?*/@property?(strong,?nonatomic)?NSNumber?*money;/**?性別?*/@property?(assign,?nonatomic)?Sex?sex;/**?同性戀?*/@property?(assign,?nonatomic,?getter=isGay)?BOOL?gay;@end最直接的方法是:
?User?*user?=?[[User?alloc]?init];user.name?=?dict[@"name"];user.icon?=?dict[@"icon"];....假如屬性數(shù)量一多,人工手寫大量樣板代碼將耗費大量時間和精力,毫無意義.
如果要寫一個框架自動幫我們轉(zhuǎn)模型出來,大致思路如下:
1.遍歷模型中的屬性,然后拿到屬性名作為鍵值去字典中尋找值.
2.找到值后根據(jù)模型的屬性的類型將值轉(zhuǎn)成正確的類型
3.賦值
首先進行第一步:
遍歷模型中的屬性,然后拿到屬性名作為鍵值去字典中尋找值.
方法偽代碼:
[模型類?遍歷屬性的方法];為了方便使用,創(chuàng)建一個叫NSObject+Property的分類.寫一個獲取所有屬性的方法.
@interface?NSObject?(Property)+?(NSArray?*)properties;@end假設(shè)我們看不見一個類的.h和.m,有什么辦法可以獲取它所有的實例變量呢?答案是通過運行時機制.當在實現(xiàn)+ (NSArray *)properties方法時,需要導入運行時庫.然后使用庫中的API提供的函數(shù)得到一個類的方法列表.
注:在舊版本的MJExtension中,獲取成員變量是通過class_copyIvarList來獲取的類的所有實例變量,根據(jù)MJ源碼中的說明:"在 swift 中,由于語法結(jié)構(gòu)的變化,使用 Ivar 非常不穩(wěn)定,經(jīng)常會崩潰!",所以改用了獲取成員屬性的方法.
另外,不管是獲取成員屬性還是實例變量,都不能獲取到父類的列表.(本人忽略了對父類成員屬性的獲取,后期更新中會更新這一失誤).
//?Any?instance?variables?declared?by?superclasses?are?not?included.objc_property_t?*class_copyPropertyList(Class?cls,?unsigned?int?*outCount)返回的是叫objc_property_t的一個結(jié)構(gòu)體指針,并且通過傳入值引用能夠得到屬性的個數(shù).
#import?"NSObject+Property.h"#import?<objc/runtime.h>@implementation?NSObject?(Property)+?(NSArray?*)properties{????NSArray?*propertiesArray?=?[NSMutableArray?array];??????//?1.獲得所有的屬性unsigned?int?outCount?=?0;objc_property_t?*properties?=?class_copyPropertyList(self,?&outCount);??????//?.....return?propertiesArray; }@end來到這里已經(jīng)獲取到了屬性列表,那么objc_property_t指向的結(jié)構(gòu)體內(nèi)部是怎樣的呢.通過搜尋<objc/runtime.h>頭文件并看不到objc_property_t的定義的.但好在runtime開源,我們搜尋到了相關(guān)的定義.
typedef?struct?property_t?*objc_property_t;struct?property_t?{????const?char?*name;????const?char?*attributes; };由于知道了結(jié)構(gòu)體的內(nèi)部構(gòu)造,就可以獲取內(nèi)部的成員變量.例如以下方法:
typedef?struct?property_t?{????const?char?*name;????const?char?*attributes; }?*propertyStruct;@implementation?NSObject?(Property)+?(NSArray?*)properties{????NSArray?*propertiesArray?=?[NSMutableArray?array];????//?1.獲得所有的屬性unsigned?int?outCount?=?0;objc_property_t?*properties?=?class_copyPropertyList(self,?&outCount);????for?(int?i?=?0;?i?<?outCount;?i++)?{objc_property_t?property?=?properties[i];????????NSLog(@"name:%s---attributes:%s",((propertyStruct)property)->name,((propertyStruct)property)->attributes);}????return?propertiesArray; }@end在外部調(diào)用+ (NSArray *)properties方法能夠打印出一個類的全部屬性,如:
NSArray?*propertyArray?=?[User?properties];得到控制臺輸出:
從輸出中可以看到該結(jié)構(gòu)體的name成員表示成員屬性的名字,attributes表示成員屬性中的一些特性(如是什么類,原子性還是非原子性,是strong還是weak還是copy,生成的成員變量名等信息)...
從蘋果的官方文檔(Objective-C Runtime Programming Guide)可以得知,attributes是一個類型編碼字符串.可以使用property_getAttributes函數(shù)獲得這個類型編碼字符串.這個字符串以T作為開始,接上@encode 類型編碼和一個逗號,以V接上實例變量名作為結(jié)尾,在它們之間是一些其他信息,以逗號分割.具體內(nèi)容可以看官方文檔中詳細的表格.
在實際賦值過程中,我們并不用關(guān)心該屬性的內(nèi)存管理語義,生成的成員變量名,或者其他什么信息.在attributes中,只需要知道它所屬的類或者是什么基本數(shù)據(jù)類型,即T至第一個逗號之前中間的內(nèi)容,如果是類的話還需要將@"和"去掉.
實際上,框架提供的運行時庫已經(jīng)給我們提供獲取屬性名和屬性特性的函數(shù)了.通過下面方式也能打印出相同結(jié)果.
NSLog(@"name:%s---attributes:%s",property_getName(property),property_getAttributes(property));從runtime源碼中可以看到這兩個函數(shù)的內(nèi)部是這樣實現(xiàn)的:
const?char?*property_getName(objc_property_t?prop){????return?prop->name; }const?char?*property_getAttributes(objc_property_t?prop){????return?prop->attributes; }再回顧前面說的思路,這時會更清晰:
1.拿到模型的屬性名(注意屬性名和成員變量名的區(qū)別),和對應(yīng)的數(shù)據(jù)類型.
2.用該屬性名作為鍵去字典中尋找對應(yīng)的值.
3.拿到值后將值轉(zhuǎn)換為屬性對應(yīng)的數(shù)據(jù)類型.
4.賦值.
現(xiàn)在已經(jīng)進行到第一步,并且拿到了屬性名,但是數(shù)據(jù)類型還要進一步截取,截取方法如下:
for?(int?i?=?0;?i?<?outCount;?i++)?{objc_property_t?property?=?properties[i];??????????//?為了以后方便,將C字符串轉(zhuǎn)換成OC對象NSString?*name?=?@(property_getName(property));????????NSString?*attributes?=?@(property_getAttributes(property));????????NSUInteger?loc?=?1;????????NSUInteger?len?=?[attributes?rangeOfString:@","].location?-?loc;????????NSString?*type?=?[attributes?substringWithRange:NSMakeRange(loc,?len)];????????NSLog(@"%@",type);}控制臺結(jié)果顯示我們能夠截取到其中的類型了.
該部分源碼請看項目實例代碼中的<打印類型>
回歸我們拿到這些數(shù)據(jù)類型的初衷,是為了是用字典中的值的類型與模型中屬性的類型進行對比,想要對比,需要拿到屬性的類型,因此需要將這些編碼轉(zhuǎn)換成一個表示類型的類,創(chuàng)建一個類用來包裝類型.
/***??包裝一種類型*/@interface?MJPropertyType?:?NSObject/**?是否為id類型?*/@property?(nonatomic,?readonly,?getter=isIdType)?BOOL?idType;/**?是否為基本數(shù)字類型:int、float等?*/@property?(nonatomic,?readonly,?getter=isNumberType)?BOOL?numberType;/**?是否為BOOL類型?*/@property?(nonatomic,?readonly,?getter=isBoolType)?BOOL?boolType;/**?對象類型(如果是基本數(shù)據(jù)類型,此值為nil)?*/@property?(nonatomic,?readonly)?Class?typeClass;@endOC對象可以通過Class來表示類型,而基本數(shù)據(jù)類型只能用布爾來標識.
把這些名字和類型遍歷出來,肯定是為了以后有用,所以需要把它們存起來,由于它們是一個"整體",所以還是設(shè)計一個類將他們包裝起來比較好.創(chuàng)建一個包裝成員屬性的類—MJProperty.
@interface?MJProperty?:?NSObject/**?成員屬性的名字?*/@property?(nonatomic,?readonly)?NSString?*name;/**?成員屬性的類型?*/@property?(nonatomic,?readonly)?MJPropertyType?*type;@end這時,代碼就可以進行重構(gòu)了,將屬于不同類的功能封裝到對應(yīng)的類上,讓MJProperty提供一個類方法用于返回一個將objc_property_t進行包裝的類.
?for?(int?i?=?0;?i?<?outCount;?i++)?{????????objc_property_t?property?=?properties[i];MJProperty?*propertyObj?=?[MJProperty?propertyWithProperty:property];}propertyWithProperty:方法的實現(xiàn)如下:
+?(instancetype)propertyWithProperty:(objc_property_t)property{????return??[[MJProperty?alloc]?initWithProperty:property]; }-?(instancetype)initWithProperty:(objc_property_t)property{????if?(self?=?[super?init])?{_name?=?@(property_getName(property));_type?=?[MJPropertyType?propertyTypeWithAttributeString:@(property_getAttributes(property))];;}????return?self; }MJPropertyType也提供類方法用于包裝類型:
+?(instancetype)propertyTypeWithAttributeString:(NSString?*)string{????return?[[MJPropertyType?alloc]?initWithTypeString:string]; }-?(instancetype)initWithTypeString:(NSString?*)string {????if?(self?=?[super?init]){????????NSUInteger?loc?=?1;????????NSUInteger?len?=?[string?rangeOfString:@","].location?-?loc;????????NSString?*type?=?[string?substringWithRange:NSMakeRange(loc,?len)];????????NSLog(@"%@",type);}????return?self; }重構(gòu)完成之后,結(jié)構(gòu)顯得更加清晰.更有利于接下來的工作.下面繼續(xù)完成type的提取.
該部分源碼請看項目實例代碼中的<重構(gòu)>
上面獲取到的這些類型,是類型編碼,在蘋果文檔中告訴了我們編碼對應(yīng)的類型:
根據(jù)這個對應(yīng)關(guān)系的圖表,我們將常用的幾個編碼定義成常量字符串或者宏表示它所對應(yīng)的類型,便于編碼和閱讀:
/***??成員變量類型(屬性類型)*/NSString?*const?MJPropertyTypeInt?=?@"i";NSString?*const?MJPropertyTypeShort?=?@"s";NSString?*const?MJPropertyTypeFloat?=?@"f";NSString?*const?MJPropertyTypeDouble?=?@"d";NSString?*const?MJPropertyTypeLong?=?@"q";NSString?*const?MJPropertyTypeChar?=?@"c";NSString?*const?MJPropertyTypeBOOL1?=?@"c";NSString?*const?MJPropertyTypeBOOL2?=?@"b";NSString?*const?MJPropertyTypePointer?=?@"*";NSString?*const?MJPropertyTypeIvar?=?@"^{objc_ivar=}";NSString?*const?MJPropertyTypeMethod?=?@"^{objc_method=}";NSString?*const?MJPropertyTypeBlock?=?@"@?";NSString?*const?MJPropertyTypeClass?=?@"#";NSString?*const?MJPropertyTypeSEL?=?@":";NSString?*const?MJPropertyTypeId?=?@"@";設(shè)置完后,就可以進行提取類型了.
-?(instancetype)initWithTypeString:(NSString?*)string {????if?(self?=?[super?init]){????????NSUInteger?loc?=?1;????????NSUInteger?len?=?[string?rangeOfString:@","].location?-?loc;????????NSString?*typeCode?=?[string?substringWithRange:NSMakeRange(loc,?len)];[self?getTypeCode:typeCode];????????NSLog(@"%@",typeCode);}????return?self; }-?(void)getTypeCode:(NSString?*)code {????if?([code?isEqualToString:MJPropertyTypeId])?{_idType?=?YES;}?else?if?(code.length?>?3?&&?[code?hasPrefix:@"@\""])?{????????//?去掉@"和",截取中間的類型名稱_code?=?[code?substringWithRange:NSMakeRange(2,?code.length?-?3)];_typeClass?=?NSClassFromString(_code);_numberType?=?(_typeClass?==?[NSNumber?class]?||?[_typeClass?isSubclassOfClass:[NSNumber?class]]);}????//?是否為數(shù)字類型NSString?*lowerCode?=?_code.lowercaseString;????NSArray?*numberTypes?=?@[MJPropertyTypeInt,?MJPropertyTypeShort,?MJPropertyTypeBOOL1,?MJPropertyTypeBOOL2,?MJPropertyTypeFloat,?MJPropertyTypeDouble,?MJPropertyTypeLong,?MJPropertyTypeChar];????if?([numberTypes?containsObject:lowerCode])?{_numberType?=?YES;????????if?([lowerCode?isEqualToString:MJPropertyTypeBOOL1]||?[lowerCode?isEqualToString:MJPropertyTypeBOOL2])?{_boolType?=?YES;}} }至此,一個MJProperty的骨架就大致搭好了.
該部分源碼請看項目實例代碼中的<MJProperty的構(gòu)建>
13F73F26-1195-43BC-BC98-FF2641B7DA58.png
當想要使用字典轉(zhuǎn)模型的功能時,提供一個類方法方便轉(zhuǎn)換,該方法放在NSObject+keyValue2object分類中,該分類負責字典轉(zhuǎn)模型的方法實現(xiàn).
@implementation?NSObject?(keyValue2object)+?(instancetype)objectWithKeyValues:(id)keyValues{????if?(!keyValues)?return?nil;????return?[[[self?alloc]?init]?setKeyValues:keyValues]; }-?(instancetype)setKeyValues:(id)keyValues{????NSArray?*propertiesArray?=?[self.class?properties];????for?(MJProperty?*property?in?propertiesArray)?{MJPropertyType?*type?=?property.type;Class?typeClass?=?type.typeClass;????????if?(type.isBoolType)?{????????????NSLog(@"bool");}else?if?(type.isIdType){????????????NSLog(@"ID");}else?if?(type.isNumberType){????????????NSLog(@"Number");}else{????????????NSLog(@"%@",typeClass);}}????return?self; }@end打印結(jié)果:
然后進行下一步----2.用該屬性名作為鍵去字典中尋找對應(yīng)的值.
?id?value?=?[keyValues?valueForKey:property.name];?if?(!value)?continue;接下來是第三步:3.拿到值后將值的類型轉(zhuǎn)換為屬性對應(yīng)的數(shù)據(jù)類型.
首先處理數(shù)字類型,如果模型的屬性是數(shù)字類型,即type.isNumberType == YES.如果字典中的值是字符串類型的,需要將其轉(zhuǎn)成NSNumber類型.如果本來就是基本數(shù)據(jù)類型,則不用進行任何轉(zhuǎn)換.
if?(type.isNumberType){//?字符串->數(shù)字if?([value?isKindOfClass:[NSString?class]])????????value?=?[[[NSNumberFormatter?alloc]init]?numberFromString:value]; }其中有一種情況,是需要進行特殊處理的.當模型的屬性是char類型或者bool類型時,獲取到的編碼都為c,并且bool還有可能是B編碼,它們都對應(yīng)_boolType.因為數(shù)字類型包含布爾類型,所以bool類型要在數(shù)字類型的條件下進行額外判斷.
if?(type.isNumberType){NSString?*oldValue?=?value;????????????//?字符串->數(shù)字if?([value?isKindOfClass:[NSString?class]]){value?=?[[[NSNumberFormatter?alloc]?init]?numberFromString:value];????????????????if?(type.isBoolType)?{NSString?*lower?=?[oldValue?lowercaseString];????????????????????if?([lower?isEqualToString:@"yes"]?||?[lower?isEqualToString:@"true"]?)?{value?=?@YES;}?else?if?([lower?isEqualToString:@"no"]?||?[lower?isEqualToString:@"false"])?{value?=?@NO;}}}}然后處理其他類型轉(zhuǎn)成字符串類型的情況.
else{????????????if?(typeClass?==?[NSString?class])?{if?([value?isKindOfClass:[NSNumber?class]])?{if?(type.isNumberType)????????????????????????//?NSNumber?->?NSStringvalue?=?[value?description];}else?if?([value?isKindOfClass:[NSURL?class]]){//?NSURL?->?NSStringvalue?=?[value?absoluteString];}}}最后,進行賦值.
[self?setValue:value?forKey:property.name];最簡單的字典轉(zhuǎn)模型大致完成了,當然,還有很多細節(jié)沒有完善,但細節(jié)總是隨著需求的不斷變化而不斷增加的.
該部分源碼請看項目實例代碼中的<簡單的字典轉(zhuǎn)模型>
JSON字符串 -> 模型
定義一個JSON字符串轉(zhuǎn)成模型:
/***??JSON字符串?->?模型*/ void?keyValues2object1(){//?1.定義一個JSON字符串NSString?*jsonString?=?@"{\"name\":\"Jack\",?\"icon\":\"lufy.png\",?\"age\":20}";//?2.將JSON字符串轉(zhuǎn)為User模型User?*user?=?[User?objectWithKeyValues:jsonString];//?3.打印User模型的屬性NSLog(@"name=%@,?icon=%@,?age=%d",?user.name,?user.icon,?user.age);}這時程序會崩潰,因為沒有對程序原來只對字典類型作處理:
//?如果是字符串,到這行就崩了id?value?=?[keyValues?valueForKey:property.name];所以在這之前需要將JSON轉(zhuǎn)成Foundation框架中的對象,蘋果提供了強大的NSJSONSerialization.利用它,在剛開始傳入字典/JSON字符串的時候?qū)⑵溥M行轉(zhuǎn)換.
-?(instancetype)setKeyValues:(id)keyValues{keyValues?=?[keyValues?JSONObject]; ...... }該方法的具體實現(xiàn)如下,如果是NSString,就要先轉(zhuǎn)成NSData再進行序列化.
-?(id)JSONObject{????id?foundationObj;????if?([self?isKindOfClass:[NSString?class]])?{foundationObj?=?[NSJSONSerialization?JSONObjectWithData:[(NSString?*)self?dataUsingEncoding:NSUTF8StringEncoding]?options:kNilOptions?error:nil];}else?if?([self?isKindOfClass:[NSData?class]]){foundationObj?=?[NSJSONSerialization?JSONObjectWithData:(NSData?*)self?options:kNilOptions?error:nil];}????return?foundationObj?:self; }該部分源碼請看項目實例代碼中的<JSON轉(zhuǎn)模型>
復雜的字典 -> 模型
定義一個模型中包含模型的復雜字典:
NSDictionary?*dict?=?@{???????????????????????????@"text"?:?@"是啊,今天天氣確實不錯!",???????????????????????????@"user"?:?@{???????????????????????????????????@"name"?:?@"Jack",???????????????????????????????????@"icon"?:?@"lufy.png"},???????????????????????????@"retweetedStatus"?:?@{???????????????????????????????????@"text"?:?@"今天天氣真不錯!",???????????????????????????????????@"user"?:?@{???????????????????????????????????????????@"name"?:?@"Rose",???????????????????????????????????????????@"icon"?:?@"nami.png"}}};對待這種字典的思路,應(yīng)該想到遞歸,當碰到模型中的屬性類型是一個模型類時,將字典中的值(Value)作為字典處理.然后再調(diào)用字典轉(zhuǎn)模型的方法返回一個模型類.所以在包裝類型時還要有個屬性表示它是否是自定義的模型類,才能作為依據(jù)繼續(xù)遞歸.判斷的方法是看它是否是來自于Foundation框架的類.
/**?類型是否來自于Foundation框架,比如NSString、NSArray?*/@property?(nonatomic,?readonly,?getter?=?isFromFoundation)?BOOL?fromFoundation;在提取類型的方法中添加這樣一條:
else?if?(code.length?>?3?&&?[code?hasPrefix:@"@\""])?{????????//?去掉@"和",截取中間的類型名稱_code?=?[code?substringWithRange:NSMakeRange(2,?code.length?-?3)];_typeClass?=?NSClassFromString(_code);_numberType?=?(_typeClass?==?[NSNumber?class]?||?[_typeClass?isSubclassOfClass:[NSNumber?class]]);??????????//?判斷是否是模型類_fromFoundation?=?[NSObject?isClassFromFoundation:_typeClass];}怎么判斷是否來自Foundation框架呢? 下圖展示了Foundation框架(NSObject部分)下的類結(jié)構(gòu).
用一個NSSet(比用NSArray檢索效率更高),返回一些常用基本的Foundation框架下繼承自NSObject的類.
static?NSSet?*foundationClasses_;+?(NSSet?*)foundationClasses {????if?(foundationClasses_?==?nil)?{foundationClasses_?=?[NSSet?setWithObjects:[NSURL?class],[NSDate?class],[NSValue?class],[NSData?class],[NSArray?class],[NSDictionary?class],[NSString?class],[NSAttributedString?class],?nil];}????return?foundationClasses_; }具體isClassFromFoundation的邏輯由類方法實現(xiàn),在上面的集合中遍歷.由于幾乎所有類都是繼承自NSObject,所以NSObject不能寫入上面的集合當中,需要額外判斷:
+?(BOOL)isClassFromFoundation:(Class)c{????if?(c?==?[NSObject?class])?return?YES;__block?BOOL?result?=?NO;[[self?foundationClasses]?enumerateObjectsUsingBlock:^(Class?foundationClass,?BOOL?*stop)?{????????if?([c?isSubclassOfClass:foundationClass])?{result?=?YES;*stop?=?YES;}}];????return?result; }得到結(jié)果后,需要在setKeyValues:keyValues這一核心方法中添加是否為模型類的判斷:
//?如果不是來自foundation框架的類并且不是基本數(shù)據(jù)類型?,則遞歸if?(!type.isFromFoundation?&&?typeClass)?{value?=?[typeClass?objectWithKeyValues:value];}該部分源碼請看項目實例代碼中的<復雜字典轉(zhuǎn)模型>
字典數(shù)組 -> 模型
稍復雜的一種情況是字典里裝有數(shù)組的情況.
NSDictionary?*dict?=?@{???????????????????????????@"statuses"?:?@[@{???????????????????????????????????????@"text"?:?@"今天天氣真不錯!",???????????????????????????????????????@"user"?:?@{???????????????????????????????????????????????@"name"?:?@"Rose",???????????????????????????????????????????????@"icon"?:?@"nami.png"}},@{???????????????????????????????????????@"text"?:?@"明天去旅游了",???????????????????????????????????????@"user"?:?@{???????????????????????????????????????????????@"name"?:?@"Jack",???????????????????????????????????????????????@"icon"?:?@"lufy.png"}}],???????????????????????????@"ads"?:?@[@{???????????????????????????????????????@"image"?:?@"ad01.png",???????????????????????????????????????@"url"?:?@"http://www.小碼哥ad01.com"},@{???????????????????????????????????????@"image"?:?@"ad02.png",???????????????????????????????????????@"url"?:?@"http://www.小碼哥ad02.com"}],???????????????????????????@"totalNumber"?:?@"2014",???????????????????????????@"previousCursor"?:?@"13476589",???????????????????????????@"nextCursor"?:?@"13476599"};上面定義了一個字典,模型StatusResult有兩個數(shù)組屬性.
@interface?StatusResult?:?BaseObject/**?存放著某一頁微博數(shù)據(jù)(里面都是Status模型)?*/@property?(strong,?nonatomic)?NSMutableArray?*statuses;/**?存放著一堆的廣告數(shù)據(jù)(里面都是Ad模型)?*/@property?(strong,?nonatomic)?NSArray?*ads;/**?總數(shù)?*/@property?(strong,?nonatomic)?NSNumber?*totalNumber;/**?上一頁的游標?*/@property?(assign,?nonatomic)?long?long?previousCursor;/**?下一頁的游標?*/@property?(assign,?nonatomic)?long?long?nextCursor;@end對于一個數(shù)組來說,你必須要告訴方法里面裝的是什么模型,才能將字典中值為數(shù)組的成員轉(zhuǎn)成模型.
在MJExtension中,提供了兩種方式進行處理.
方式一,調(diào)用NSObject分類中得類方法:
[StatusResult?setupObjectClassInArray:^NSDictionary?*{return?@{@"statuses"?:?@"Status",//?或者?@"statuses"?:?[Status?class],@"ads"?:?@"Ad"//?或者?@"ads"?:?[Ad?class]};}];方式二,在模型的.m文件中實現(xiàn)方法供回調(diào):
+?(NSDictionary?*)objectClassInArray {????return?@{?????????????@"statuses"?:?@"Status",??????????????//?或者?@"statuses"?:?[Status?class],@"ads"?:?@"Ad"//?或者?@"ads"?:?[Ad?class]}; }原理上都差不多,都是通過代碼進行回調(diào),這個主要實現(xiàn)方式二.
在分類中聲明一個protocol提供接口供模型類調(diào)用.
@protocol?MJKeyValue?<NSObject>+?(NSDictionary?*)?objectClassInArray;@end在轉(zhuǎn)換的代碼中設(shè)置添加設(shè)置數(shù)組模型的方法:
if?(!type.isFromFoundation?&&?typeClass)?{value?=?[typeClass?objectWithKeyValues:value]; }//?看該類是否實現(xiàn)了objectClassInArray方法else?if?([self.class?respondsToSelector:@selector(objectClassInArray)]){id?objectClass;????//?如果是class類型,例如@"statuses"?:?[Status?class]objectClass?=?[self.class?objectClassInArray][property.name];????//?如果是NSString類型,例如@"statuses"?:?@"Status"if?([objectClass?isKindOfClass:[NSString?class]])?{objectClass?=?NSClassFromString(objectClass);}????//?如果有值if?(objectClass)?{????????//?返回一個裝了模型的數(shù)組value?=?[objectClass?objectArrayWithKeyValuesArray:value];}}這時返回的值當然是個裝滿模型的數(shù)組模型.思路也很簡單,對數(shù)組里的每一個成員都進行字典轉(zhuǎn)模型的方法.如果其中的成員不是自定義模型類,那么直接返回.
+?(NSMutableArray?*)objectArrayWithKeyValuesArray:(id)keyValuesArray{????if?([self?isClassFromFoundation:self])????????return?keyValuesArray;????//?如果是json字符串,轉(zhuǎn)成字典keyValuesArray?=?[keyValuesArray?JSONObject];????NSMutableArray?*modelArray?=?[NSMutableArray?array];????//?遍歷for?(NSDictionary?*keyValues?in?keyValuesArray)?{????????//?對其中的模型調(diào)用字典轉(zhuǎn)模型方法,并添加到數(shù)組中返回id?model;model?=?[self?objectWithKeyValues:keyValues];????????if?(model)?{[modelArray?addObject:model];}}????return?modelArray;}該部分源碼請看項目實例代碼中的<字典數(shù)組轉(zhuǎn)模型>
key的替換
@interface?IDAndDescription?:?NSObject@property?(nonatomic,?copy)?NSString?*ID;@property?(nonatomic,?copy)?NSString?*Description;@end實際開發(fā)中,服務(wù)器通常返回一個字段名為id,或者description的JSON數(shù)據(jù),而這兩個名字在OC中有特殊含義,如上所示,在定義屬性的時候并不能使用這類名稱.這時屬性名與字典key不再是直接對應(yīng)的關(guān)系,需要加入一層轉(zhuǎn)換.
源碼中key的替換也有幾種方式選擇,這里實現(xiàn)replacedKeyFromPropertyName這一方式.
過程是在要替換key的模型類中實現(xiàn)replacedKeyFromPropertyName方法,返回一個原始key和更名的key對應(yīng)的字典.replacedKeyFromPropertyName在protocol中聲明.
實際上,也就是創(chuàng)建了一個方法來獲取屬性名與字典key的對應(yīng)關(guān)系.
在模型類中實現(xiàn)接口中的方法告知對應(yīng)關(guān)系.
@implementation?IDAndDescription+?(NSDictionary?*)replacedKeyFromPropertyName{????return?@{?????????????@"ID"?:?@"id",?????????????@"Description"?:?@"description"}; }@end該方法從字典中需找要替換的key,參數(shù)是property的名字.如果字典中找不到對應(yīng)的屬性名,則不需要進行轉(zhuǎn)換.
+?(NSString?*)propertyKey:(NSString?*)propertyName{????NSString?*key;????if?([self?respondsToSelector:@selector(replacedKeyFromPropertyName)])?{key?=?[self?replacedKeyFromPropertyName][propertyName];}????return?key?:propertyName; }在獲取值(value)的時候,要將key替換成對應(yīng)的key.
id?value?=?[keyValues?valueForKey:[self.class?propertyKey:property.name]];if?(!value)?continue;轉(zhuǎn)換完成.
性能優(yōu)化
將5個字典轉(zhuǎn)模型的例子同時進行運行,在+ properties方法中添加一句打印.另外之前的例子都是有內(nèi)存泄露的,這里添加了free(properties)修復了這個問題.
+?(NSArray?*)properties{NSLog(@"%@調(diào)用了properties方法",[self?class]);NSMutableArray?*propertiesArray?=?[NSMutableArray?array];????//?1.獲得所有的屬性unsigned?int?outCount?=?0;????objc_property_t?*properties?=?class_copyPropertyList(self,?&outCount);????for?(int?i?=?0;?i?<?outCount;?i++)?{????????objc_property_t?property?=?properties[i];MJProperty?*propertyObj?=?[MJProperty?propertyWithProperty:property];[propertiesArray?addObject:propertyObj];}????free(properties);????return?propertiesArray; }輸出臺輸出如下:
可以看到,很多的類都不止一次調(diào)用了獲取屬性的方法,對于一個類來說,要獲取它的全部屬性,只要獲取一次就夠了.獲取到后將結(jié)果緩存起來,下次就不必進行不必要的計算.
注意:由于我寫文章時手上的這份源碼相對較早,緩存屬性列表是通過一個全局字典來緩存的,而在最新版本的MJExtension中,已經(jīng)換成了關(guān)聯(lián)對象來實現(xiàn).由于實現(xiàn)思路大致都是一樣,并且效果相同,所以這里并不糾結(jié)用哪種方式.
//?設(shè)置一個全局字典用來將類的屬性都緩存起來static?NSMutableDictionary?*cachedProperties_; +?(void)load {cachedProperties_?=?[NSMutableDictionary?dictionary]; }將方法改寫為:
+?(NSArray?*)properties {????NSMutableArray?*cachedProperties?=?cachedProperties_[NSStringFromClass(self)];????if?(!cachedProperties)?{????????NSLog(@"%@調(diào)用了properties方法",[self?class]);cachedProperties?=?[NSMutableArray?array];????????//?1.獲得所有的屬性unsigned?int?outCount?=?0;objc_property_t?*properties?=?class_copyPropertyList(self,?&outCount);????????for?(int?i?=?0;?i?<?outCount;?i++)?{objc_property_t?property?=?properties[i];MJProperty?*propertyObj?=?[MJProperty?propertyWithProperty:property];[cachedProperties?addObject:propertyObj];}free(properties);cachedProperties_[NSStringFromClass(self)]?=?cachedProperties;}????return?cachedProperties; }此時控制臺輸出:
可以看每個類只經(jīng)過一次獲取全部屬性.
除了緩存屬性外,提取類型編碼的過程也可以進一步緩存優(yōu)化性能.
在下面的方法中加上一句打印:
-?(void)getTypeCode:(NSString?*)code {????NSLog(@"%@",code);...... }控制臺輸出:
可以看到一些常用的類型例如NSString多次調(diào)用了該方法.提取類型時,只要知道類名(在這里也就是typeCode),一個MJPropertyType就已經(jīng)可以確定了.
重寫了- initWithTypeString:方法:
static?NSMutableDictionary?*cachedTypes_; +?(void)load {cachedTypes_?=?[NSMutableDictionary?dictionary]; }+?(instancetype)propertyTypeWithAttributeString:(NSString?*)string{????return?[[MJPropertyType?alloc]?initWithTypeString:string]; }-?(instancetype)initWithTypeString:(NSString?*)string {????NSUInteger?loc?=?1;????NSUInteger?len?=?[string?rangeOfString:@","].location?-?loc;????NSString?*typeCode?=?[string?substringWithRange:NSMakeRange(loc,?len)];????if?(!cachedTypes_[typeCode]){????????NSLog(@"%@",typeCode);????????self?=?[super?init];[self?getTypeCode:typeCode];cachedTypes_[typeCode]?=?self;}????return?self; }輸出結(jié)果:
該部分源碼請看項目實例代碼中的<key的替換與性能優(yōu)化>
轉(zhuǎn)載于:https://my.oschina.net/daniels/blog/599141
總結(jié)
以上是生活随笔為你收集整理的跟着MJExtension实现简单的字典转模型框架 - 简书的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android数据库高手秘籍(二):创建
- 下一篇: com.alibaba.druid.sq