日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

跟着MJExtension实现简单的字典转模型框架 - 简书

發布時間:2025/3/20 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 跟着MJExtension实现简单的字典转模型框架 - 简书 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘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模型進行賦值.模型的屬性名對應字典的鍵(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"];....

假如屬性數量一多,人工手寫大量樣板代碼將耗費大量時間和精力,毫無意義.

如果要寫一個框架自動幫我們轉模型出來,大致思路如下:

1.遍歷模型中的屬性,然后拿到屬性名作為鍵值去字典中尋找值.

2.找到值后根據模型的屬性的類型將值轉成正確的類型

3.賦值


首先進行第一步:

遍歷模型中的屬性,然后拿到屬性名作為鍵值去字典中尋找值.

方法偽代碼:

[模型類?遍歷屬性的方法];

為了方便使用,創建一個叫NSObject+Property的分類.寫一個獲取所有屬性的方法.

@interface?NSObject?(Property)+?(NSArray?*)properties;@end

假設我們看不見一個類的.h和.m,有什么辦法可以獲取它所有的實例變量呢?答案是通過運行時機制.當在實現+ (NSArray *)properties方法時,需要導入運行時庫.然后使用庫中的API提供的函數得到一個類的方法列表.

注:在舊版本的MJExtension中,獲取成員變量是通過class_copyIvarList來獲取的類的所有實例變量,根據MJ源碼中的說明:"在 swift 中,由于語法結構的變化,使用 Ivar 非常不穩定,經常會崩潰!",所以改用了獲取成員屬性的方法.

另外,不管是獲取成員屬性還是實例變量,都不能獲取到父類的列表.(本人忽略了對父類成員屬性的獲取,后期更新中會更新這一失誤).

//?Any?instance?variables?declared?by?superclasses?are?not?included.objc_property_t?*class_copyPropertyList(Class?cls,?unsigned?int?*outCount)

返回的是叫objc_property_t的一個結構體指針,并且通過傳入值引用能夠得到屬性的個數.

#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

來到這里已經獲取到了屬性列表,那么objc_property_t指向的結構體內部是怎樣的呢.通過搜尋<objc/runtime.h>頭文件并看不到objc_property_t的定義的.但好在runtime開源,我們搜尋到了相關的定義.

typedef?struct?property_t?*objc_property_t;struct?property_t?{????const?char?*name;????const?char?*attributes; };

由于知道了結構體的內部構造,就可以獲取內部的成員變量.例如以下方法:

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

在外部調用+ (NSArray *)properties方法能夠打印出一個類的全部屬性,如:

NSArray?*propertyArray?=?[User?properties];

得到控制臺輸出:


從輸出中可以看到該結構體的name成員表示成員屬性的名字,attributes表示成員屬性中的一些特性(如是什么類,原子性還是非原子性,是strong還是weak還是copy,生成的成員變量名等信息)...

從蘋果的官方文檔(Objective-C Runtime Programming Guide)可以得知,attributes是一個類型編碼字符串.可以使用property_getAttributes函數獲得這個類型編碼字符串.這個字符串以T作為開始,接上@encode 類型編碼和一個逗號,以V接上實例變量名作為結尾,在它們之間是一些其他信息,以逗號分割.具體內容可以看官方文檔中詳細的表格.

在實際賦值過程中,我們并不用關心該屬性的內存管理語義,生成的成員變量名,或者其他什么信息.在attributes中,只需要知道它所屬的類或者是什么基本數據類型,即T至第一個逗號之前中間的內容,如果是類的話還需要將@"和"去掉.

實際上,框架提供的運行時庫已經給我們提供獲取屬性名和屬性特性的函數了.通過下面方式也能打印出相同結果.

NSLog(@"name:%s---attributes:%s",property_getName(property),property_getAttributes(property));

從runtime源碼中可以看到這兩個函數的內部是這樣實現的:

const?char?*property_getName(objc_property_t?prop){????return?prop->name; }const?char?*property_getAttributes(objc_property_t?prop){????return?prop->attributes; }

再回顧前面說的思路,這時會更清晰:

1.拿到模型的屬性名(注意屬性名和成員變量名的區別),和對應的數據類型.

2.用該屬性名作為鍵去字典中尋找對應的值.

3.拿到值后將值轉換為屬性對應的數據類型.

4.賦值.

現在已經進行到第一步,并且拿到了屬性名,但是數據類型還要進一步截取,截取方法如下:

for?(int?i?=?0;?i?<?outCount;?i++)?{objc_property_t?property?=?properties[i];??????????//?為了以后方便,將C字符串轉換成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);}

控制臺結果顯示我們能夠截取到其中的類型了.


該部分源碼請看項目實例代碼中的<打印類型>


回歸我們拿到這些數據類型的初衷,是為了是用字典中的值的類型與模型中屬性的類型進行對比,想要對比,需要拿到屬性的類型,因此需要將這些編碼轉換成一個表示類型的類,創建一個類用來包裝類型.

/***??包裝一種類型*/@interface?MJPropertyType?:?NSObject/**?是否為id類型?*/@property?(nonatomic,?readonly,?getter=isIdType)?BOOL?idType;/**?是否為基本數字類型:int、float等?*/@property?(nonatomic,?readonly,?getter=isNumberType)?BOOL?numberType;/**?是否為BOOL類型?*/@property?(nonatomic,?readonly,?getter=isBoolType)?BOOL?boolType;/**?對象類型(如果是基本數據類型,此值為nil)?*/@property?(nonatomic,?readonly)?Class?typeClass;@end

OC對象可以通過Class來表示類型,而基本數據類型只能用布爾來標識.

把這些名字和類型遍歷出來,肯定是為了以后有用,所以需要把它們存起來,由于它們是一個"整體",所以還是設計一個類將他們包裝起來比較好.創建一個包裝成員屬性的類—MJProperty.

@interface?MJProperty?:?NSObject/**?成員屬性的名字?*/@property?(nonatomic,?readonly)?NSString?*name;/**?成員屬性的類型?*/@property?(nonatomic,?readonly)?MJPropertyType?*type;@end

這時,代碼就可以進行重構了,將屬于不同類的功能封裝到對應的類上,讓MJProperty提供一個類方法用于返回一個將objc_property_t進行包裝的類.

?for?(int?i?=?0;?i?<?outCount;?i++)?{????????objc_property_t?property?=?properties[i];MJProperty?*propertyObj?=?[MJProperty?propertyWithProperty:property];}

propertyWithProperty:方法的實現如下:

+?(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; }

重構完成之后,結構顯得更加清晰.更有利于接下來的工作.下面繼續完成type的提取.

該部分源碼請看項目實例代碼中的<重構>



上面獲取到的這些類型,是類型編碼,在蘋果文檔中告訴了我們編碼對應的類型:


根據這個對應關系的圖表,我們將常用的幾個編碼定義成常量字符串或者宏表示它所對應的類型,便于編碼和閱讀:

/***??成員變量類型(屬性類型)*/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?=?@"@";

設置完后,就可以進行提取類型了.

-?(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]]);}????//?是否為數字類型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的構建>



13F73F26-1195-43BC-BC98-FF2641B7DA58.png

當想要使用字典轉模型的功能時,提供一個類方法方便轉換,該方法放在NSObject+keyValue2object分類中,該分類負責字典轉模型的方法實現.

@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

打印結果:


然后進行下一步----2.用該屬性名作為鍵去字典中尋找對應的值.

?id?value?=?[keyValues?valueForKey:property.name];?if?(!value)?continue;

接下來是第三步:3.拿到值后將值的類型轉換為屬性對應的數據類型.

首先處理數字類型,如果模型的屬性是數字類型,即type.isNumberType == YES.如果字典中的值是字符串類型的,需要將其轉成NSNumber類型.如果本來就是基本數據類型,則不用進行任何轉換.

if?(type.isNumberType){//?字符串->數字if?([value?isKindOfClass:[NSString?class]])????????value?=?[[[NSNumberFormatter?alloc]init]?numberFromString:value]; }

其中有一種情況,是需要進行特殊處理的.當模型的屬性是char類型或者bool類型時,獲取到的編碼都為c,并且bool還有可能是B編碼,它們都對應_boolType.因為數字類型包含布爾類型,所以bool類型要在數字類型的條件下進行額外判斷.

if?(type.isNumberType){NSString?*oldValue?=?value;????????????//?字符串->數字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;}}}}

然后處理其他類型轉成字符串類型的情況.

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];

最簡單的字典轉模型大致完成了,當然,還有很多細節沒有完善,但細節總是隨著需求的不斷變化而不斷增加的.

該部分源碼請看項目實例代碼中的<簡單的字典轉模型>

JSON字符串 -> 模型

定義一個JSON字符串轉成模型:

/***??JSON字符串?->?模型*/ void?keyValues2object1(){//?1.定義一個JSON字符串NSString?*jsonString?=?@"{\"name\":\"Jack\",?\"icon\":\"lufy.png\",?\"age\":20}";//?2.將JSON字符串轉為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轉成Foundation框架中的對象,蘋果提供了強大的NSJSONSerialization.利用它,在剛開始傳入字典/JSON字符串的時候將其進行轉換.

-?(instancetype)setKeyValues:(id)keyValues{keyValues?=?[keyValues?JSONObject]; ...... }

該方法的具體實現如下,如果是NSString,就要先轉成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轉模型>

復雜的字典 -> 模型

定義一個模型中包含模型的復雜字典:

NSDictionary?*dict?=?@{???????????????????????????@"text"?:?@"是啊,今天天氣確實不錯!",???????????????????????????@"user"?:?@{???????????????????????????????????@"name"?:?@"Jack",???????????????????????????????????@"icon"?:?@"lufy.png"},???????????????????????????@"retweetedStatus"?:?@{???????????????????????????????????@"text"?:?@"今天天氣真不錯!",???????????????????????????????????@"user"?:?@{???????????????????????????????????????????@"name"?:?@"Rose",???????????????????????????????????????????@"icon"?:?@"nami.png"}}};

對待這種字典的思路,應該想到遞歸,當碰到模型中的屬性類型是一個模型類時,將字典中的值(Value)作為字典處理.然后再調用字典轉模型的方法返回一個模型類.所以在包裝類型時還要有個屬性表示它是否是自定義的模型類,才能作為依據繼續遞歸.判斷的方法是看它是否是來自于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部分)下的類結構.


用一個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的邏輯由類方法實現,在上面的集合中遍歷.由于幾乎所有類都是繼承自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; }

得到結果后,需要在setKeyValues:keyValues這一核心方法中添加是否為模型類的判斷:

//?如果不是來自foundation框架的類并且不是基本數據類型?,則遞歸if?(!type.isFromFoundation?&&?typeClass)?{value?=?[typeClass?objectWithKeyValues:value];}

該部分源碼請看項目實例代碼中的<復雜字典轉模型>

字典數組 -> 模型

稍復雜的一種情況是字典里裝有數組的情況.

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有兩個數組屬性.

@interface?StatusResult?:?BaseObject/**?存放著某一頁微博數據(里面都是Status模型)?*/@property?(strong,?nonatomic)?NSMutableArray?*statuses;/**?存放著一堆的廣告數據(里面都是Ad模型)?*/@property?(strong,?nonatomic)?NSArray?*ads;/**?總數?*/@property?(strong,?nonatomic)?NSNumber?*totalNumber;/**?上一頁的游標?*/@property?(assign,?nonatomic)?long?long?previousCursor;/**?下一頁的游標?*/@property?(assign,?nonatomic)?long?long?nextCursor;@end

對于一個數組來說,你必須要告訴方法里面裝的是什么模型,才能將字典中值為數組的成員轉成模型.

在MJExtension中,提供了兩種方式進行處理.

方式一,調用NSObject分類中得類方法:

[StatusResult?setupObjectClassInArray:^NSDictionary?*{return?@{@"statuses"?:?@"Status",//?或者?@"statuses"?:?[Status?class],@"ads"?:?@"Ad"//?或者?@"ads"?:?[Ad?class]};}];

方式二,在模型的.m文件中實現方法供回調:

+?(NSDictionary?*)objectClassInArray {????return?@{?????????????@"statuses"?:?@"Status",??????????????//?或者?@"statuses"?:?[Status?class],@"ads"?:?@"Ad"//?或者?@"ads"?:?[Ad?class]}; }

原理上都差不多,都是通過代碼進行回調,這個主要實現方式二.

在分類中聲明一個protocol提供接口供模型類調用.

@protocol?MJKeyValue?<NSObject>+?(NSDictionary?*)?objectClassInArray;@end

在轉換的代碼中設置添加設置數組模型的方法:

if?(!type.isFromFoundation?&&?typeClass)?{value?=?[typeClass?objectWithKeyValues:value]; }//?看該類是否實現了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)?{????????//?返回一個裝了模型的數組value?=?[objectClass?objectArrayWithKeyValuesArray:value];}}

這時返回的值當然是個裝滿模型的數組模型.思路也很簡單,對數組里的每一個成員都進行字典轉模型的方法.如果其中的成員不是自定義模型類,那么直接返回.

+?(NSMutableArray?*)objectArrayWithKeyValuesArray:(id)keyValuesArray{????if?([self?isClassFromFoundation:self])????????return?keyValuesArray;????//?如果是json字符串,轉成字典keyValuesArray?=?[keyValuesArray?JSONObject];????NSMutableArray?*modelArray?=?[NSMutableArray?array];????//?遍歷for?(NSDictionary?*keyValues?in?keyValuesArray)?{????????//?對其中的模型調用字典轉模型方法,并添加到數組中返回id?model;model?=?[self?objectWithKeyValues:keyValues];????????if?(model)?{[modelArray?addObject:model];}}????return?modelArray;}

該部分源碼請看項目實例代碼中的<字典數組轉模型>


key的替換

@interface?IDAndDescription?:?NSObject@property?(nonatomic,?copy)?NSString?*ID;@property?(nonatomic,?copy)?NSString?*Description;@end

實際開發中,服務器通常返回一個字段名為id,或者description的JSON數據,而這兩個名字在OC中有特殊含義,如上所示,在定義屬性的時候并不能使用這類名稱.這時屬性名與字典key不再是直接對應的關系,需要加入一層轉換.

源碼中key的替換也有幾種方式選擇,這里實現replacedKeyFromPropertyName這一方式.

過程是在要替換key的模型類中實現replacedKeyFromPropertyName方法,返回一個原始key和更名的key對應的字典.replacedKeyFromPropertyName在protocol中聲明.

實際上,也就是創建了一個方法來獲取屬性名與字典key的對應關系.

在模型類中實現接口中的方法告知對應關系.

@implementation?IDAndDescription+?(NSDictionary?*)replacedKeyFromPropertyName{????return?@{?????????????@"ID"?:?@"id",?????????????@"Description"?:?@"description"}; }@end

該方法從字典中需找要替換的key,參數是property的名字.如果字典中找不到對應的屬性名,則不需要進行轉換.

+?(NSString?*)propertyKey:(NSString?*)propertyName{????NSString?*key;????if?([self?respondsToSelector:@selector(replacedKeyFromPropertyName)])?{key?=?[self?replacedKeyFromPropertyName][propertyName];}????return?key?:propertyName; }

在獲取值(value)的時候,要將key替換成對應的key.

id?value?=?[keyValues?valueForKey:[self.class?propertyKey:property.name]];if?(!value)?continue;

轉換完成.


性能優化

將5個字典轉模型的例子同時進行運行,在+ properties方法中添加一句打印.另外之前的例子都是有內存泄露的,這里添加了free(properties)修復了這個問題.

+?(NSArray?*)properties{NSLog(@"%@調用了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; }

輸出臺輸出如下:


可以看到,很多的類都不止一次調用了獲取屬性的方法,對于一個類來說,要獲取它的全部屬性,只要獲取一次就夠了.獲取到后將結果緩存起來,下次就不必進行不必要的計算.

注意:由于我寫文章時手上的這份源碼相對較早,緩存屬性列表是通過一個全局字典來緩存的,而在最新版本的MJExtension中,已經換成了關聯對象來實現.由于實現思路大致都是一樣,并且效果相同,所以這里并不糾結用哪種方式.

//?設置一個全局字典用來將類的屬性都緩存起來static?NSMutableDictionary?*cachedProperties_; +?(void)load {cachedProperties_?=?[NSMutableDictionary?dictionary]; }

將方法改寫為:

+?(NSArray?*)properties {????NSMutableArray?*cachedProperties?=?cachedProperties_[NSStringFromClass(self)];????if?(!cachedProperties)?{????????NSLog(@"%@調用了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; }

此時控制臺輸出:


可以看每個類只經過一次獲取全部屬性.


除了緩存屬性外,提取類型編碼的過程也可以進一步緩存優化性能.

在下面的方法中加上一句打印:

-?(void)getTypeCode:(NSString?*)code {????NSLog(@"%@",code);...... }

控制臺輸出:


可以看到一些常用的類型例如NSString多次調用了該方法.提取類型時,只要知道類名(在這里也就是typeCode),一個MJPropertyType就已經可以確定了.

重寫了- 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; }

輸出結果:


該部分源碼請看項目實例代碼中的<key的替換與性能優化>


轉載于:https://my.oschina.net/daniels/blog/599141

總結

以上是生活随笔為你收集整理的跟着MJExtension实现简单的字典转模型框架 - 简书的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。