MJExtension
是json轉(zhuǎn)模型相當(dāng)便捷的一個(gè)三方庫垄惧。本本為一窺其內(nèi)部奧妙缝驳,文中肯定有不足之處慈俯,敬請(qǐng)指正栏尚。json 的一個(gè)
{}
就是一個(gè)json對(duì)象起愈,對(duì)應(yīng)iOS開發(fā)json數(shù)據(jù)可類比NSDictionary,{}
就可看做一個(gè)模型類,其他json類型就可看做這個(gè)模型類的屬性。解析是{}
解析為NSDictionary
,[]
為NSArray
,字符串為NSString
,數(shù)字類型為NSNumber
抬虽。我平時(shí)常用MJExtension官觅。其他的比如YYModel,功能上要豐富一些阐污。這里就先看看MJ啦休涤。
-
來看看MJ的結(jié)構(gòu):
MJExtension
如果屬性名和json數(shù)據(jù)中的key完全相同,核心模塊主要就是紅色部分笛辟。
功能實(shí)現(xiàn)都是以
NSObject
的分類實(shí)現(xiàn)功氨,所以任何繼承自NSObject
的子類都可調(diào)用。我們先來看看如何提供一些自定義功能的隘膘,有兩種方式:一者通過在模型類實(shí)現(xiàn)
MJKeyValue
協(xié)議疑故,一者通過NSObject+MJProperty
和NSObject+MJClass
中對(duì)應(yīng)的類方法。以下只列出設(shè)置屬性白名單的代碼弯菊,其他請(qǐng)自行查看,處理方式大同小異踱阿。
協(xié)議
@protocol MJKeyValue <NSObject>
@optional
/**
* 只有這個(gè)數(shù)組中的屬性名才允許進(jìn)行字典和模型的轉(zhuǎn)換
*/
+ (NSArray *)mj_allowedPropertyNames;
@end
因?yàn)?code>NSObject+MJKeyValue分類已經(jīng)遵守了協(xié)議管钳,所以模型類可以直接實(shí)現(xiàn)就是。
類方法
先看調(diào)用:
[Person mj_setupAllowedPropertyNames:^NSArray *{
return @[@"name", @"sex"];
}];
- 其實(shí)這個(gè)方法類似一個(gè)setter方法,不過這里是類方法的形式
/**
* 屬性白名單配置
*/
+ (void)mj_setupAllowedPropertyNames:
(MJAllowedPropertyNames)allowedPropertyNames {
// 內(nèi)部就是將block返回的數(shù)組綁定到MJAllowedPropertyNamesKey
[self mj_setupBlockReturnValue:allowedPropertyNames key:&MJAllowedPropertyNamesKey];
}
/**
* 利用 runtime 通過 kvc 方式綁定設(shè)置的屬性白名單到模型類,否則為nil
*/
+ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key {
if (block) {
objc_setAssociatedObject(self, key, block(), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} else {
objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[[self dictForKey:key] removeAllObjects];
}
/**
* 根據(jù)key返回對(duì)應(yīng)的保存自定義數(shù)據(jù)的字典
*/
+ (NSMutableDictionary *)dictForKey:(const void *)key {
@synchronized (self) {
if (key == &MJAllowedPropertyNamesKey)
return allowedPropertyNamesDict_;
if (key == &MJIgnoredPropertyNamesKey)
return ignoredPropertyNamesDict_;
if (key == &MJAllowedCodingPropertyNamesKey)
return allowedCodingPropertyNamesDict_;
if (key == &MJIgnoredCodingPropertyNamesKey)
return ignoredCodingPropertyNamesDict_;
return nil;
}
}
objc_setAssociatedObject
設(shè)置關(guān)聯(lián)软舌,對(duì)應(yīng)objc_getAssociatedObject
就是獲取關(guān)聯(lián)值才漆。這里用到了
runtime
的關(guān)聯(lián)機(jī)制,常撤鸬悖看到在分類中添加屬性就是利用這個(gè)機(jī)制醇滥。因?yàn)檫@種方式是基于key
的,所以MJ聲明了一些靜態(tài)全局常量超营。這里展示的MJAllowedPropertyNamesKey
就是一個(gè)char
型靜態(tài)全局常量鸳玩,作用是作為關(guān)聯(lián)block返回值的key。當(dāng)然還有其他的key演闭,這里不一一列舉不跟。同時(shí)也聲明一個(gè)靜態(tài)全局字典
allowedPropertyNamesDict_
,目的是以當(dāng)前類名作為key
米碰,將當(dāng)前類及父類中關(guān)聯(lián)的block返回值保存窝革。這里字典的初始化MJ放在了分類的
+(void)load
方法中,該方法只在App啟動(dòng)時(shí)加載一次吕座。如果父類虐译,子類和分類都實(shí)現(xiàn)了該方法,執(zhí)行順序是父類>子類>分類吴趴。執(zhí)行該方法時(shí)漆诽,程序必定會(huì)阻塞,所以要盡量少的在其中執(zhí)行任務(wù)。最后拿到字典進(jìn)行清空拴泌,筆者沒猜到意圖魏身。牽強(qiáng)地推測(cè)下:
allowedPropertyNamesDict_
是全局的,如果有同一個(gè)類重復(fù)調(diào)用setup方法蚪腐,新的數(shù)據(jù)會(huì)追加在字典中箭昵。而如果存在二次調(diào)用,應(yīng)該是不需要之前的自定義數(shù)據(jù)才是(直接更新自定義就是唄···)回季。另外
+dictForKey
方法在NSObject+MJProperty
和NSObject+MJClass
中都有家制,筆者在調(diào)試時(shí)遇到了點(diǎn)小問題。比如在MJClass
中執(zhí)行時(shí)泡一,step in
會(huì)跳到MJProperty
中的該方法玩敏。既然上面的方法類似setter,當(dāng)然就有類似的getter方法了茂翔。
/**
* 獲取當(dāng)前類及所有父類的白名單屬性
*/
+ (NSMutableArray *)mj_totalAllowedPropertyNames {
return [self mj_totalObjectsWithSelector:@selector(mj_allowedPropertyNames) key:&MJAllowedPropertyNamesKey];
}
/**
*
*/
+ (NSMutableArray *)mj_totalObjectsWithSelector:(SEL)selector key:(const char *)key {
// 先嘗試獲取本類的白名單凳兵,有則返回,無則通過協(xié)議或block獲取
// 其實(shí)就是 [dict objectForkey:]
NSMutableArray *array = [self dictForKey:key][NSStringFromClass(self)];
if (array) return array;
// 創(chuàng)建帖蔓、存儲(chǔ)
// 其實(shí)就是 [dict setObject: forKey:]
// 以本類名為key矮瘟,value為本類及所有父類的屬性白名單的數(shù)組
[self dictForKey:key][NSStringFromClass(self)] = array = [NSMutableArray array];
// 是否響應(yīng)白名單的協(xié)議方法
if ([self respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// 拿到白名單
NSArray *subArray = [self performSelector:selector];
#pragma clang diagnostic pop
if (subArray) {
[array addObjectsFromArray:subArray];
}
}
// 向上遍歷拿到當(dāng)前類及所有父類的白名單,并加入數(shù)組
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
NSArray *subArray = objc_getAssociatedObject(c, key);
[array addObjectsFromArray:subArray];
}];
return array;
}
- 處理自定義部分的思路:先利用runtime將預(yù)定義的全局key和自定義部分(以O(shè)C數(shù)據(jù)結(jié)構(gòu)返回塑娇,如NSArray)關(guān)聯(lián)澈侠。獲取時(shí)先嘗試從協(xié)議方法中獲取自定義部分,然后再?gòu)谋绢惢蚋割惛鶕?jù)全局key關(guān)聯(lián)的數(shù)據(jù)結(jié)構(gòu)中獲取埋酬。
-
+mj_enumerateAllClasses
遍歷當(dāng)前類及其父類哨啃,里面就是一個(gè)while循環(huán)獲取父類,就不再多說了写妥。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
#pragma clang diagnostic pop
以上3個(gè)命令是用來忽略一些編譯器警告拳球,push和pop一定搭配使用。
- 由于篇幅太多耳标,這里只看了白名單設(shè)置醇坝。所有處理自定義部分的思路基本就是上面這些,當(dāng)然具體細(xì)節(jié)還有點(diǎn)不同次坡。比如獲取時(shí)呼猪,如果即實(shí)現(xiàn)了協(xié)議方法和自定義block,白名單包括黑名單設(shè)置會(huì)將兩個(gè)部分都保存砸琅;而其他如屬性替換部分則會(huì)執(zhí)行協(xié)議方法宋距,放棄自定義block中的部分。
接下來看看json
轉(zhuǎn)model
的調(diào)用:
Pili *budaixi = [Pili mj_objectWithKeyValues:data];
- 工廠方法返回一個(gè)轉(zhuǎn)換后的模型實(shí)例症脂,來看具體實(shí)現(xiàn):
+ (instancetype)mj_objectWithKeyValues:(id)keyValues {
return [self mj_objectWithKeyValues:keyValues context:nil];
}
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context {
// 判斷傳入?yún)?shù)是否已經(jīng)解析成Json谚赎,否則使用NSJsonSerialization解析
keyValues = [keyValues mj_JSONObject];
// 判斷解析后的數(shù)據(jù)是否是字典對(duì)象,如果不是基本上json數(shù)據(jù)格式有問題了吧
// MJ自己實(shí)現(xiàn)的斷言淫僻,滿足條件繼續(xù)執(zhí)行,否則拋出錯(cuò)誤
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues參數(shù)不是一個(gè)字典");
// 接下來是和數(shù)據(jù)庫相關(guān)的
if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
}
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
- 注意
keyValues
是id
類型,當(dāng)我們從網(wǎng)絡(luò)獲取到responseData
后壶唤,可以不做處理直接傳入雳灵。因?yàn)?code>MJExtension會(huì)做json解析。 - 平常
debug
時(shí)可以嘗試使用斷言,設(shè)定一些前置條件闸盔,可以省去一些調(diào)試步驟悯辙。一般使用系統(tǒng)的NSAssert
即可 - 最后初始化并調(diào)用實(shí)例方法進(jìn)行轉(zhuǎn)換
先看看如何做的json解析
- (id)mj_JSONObject
{
if ([self isKindOfClass:[NSString class]]) {
// 傳入的是一個(gè)json字符串,類似@"{\"name\":\"yaya\"}",先轉(zhuǎn)成Data
return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
} else if ([self isKindOfClass:[NSData class]]) {
// 如果外部沒解析過數(shù)據(jù)迎吵,在這處理
return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
}
// 模型 -> 字典
return self.mj_keyValues;
}
- 該方法有兩個(gè)作用:一是對(duì)源json數(shù)據(jù)進(jìn)行解析躲撰;二是對(duì)自定義模型轉(zhuǎn)字典,如果不是自定義模型則直接返回原數(shù)據(jù)
- 能使用
self.mj_keyValues
并不是聲明了一個(gè)屬性击费,而是直接寫了一個(gè)getter
形式的方法拢蛋。目的是用于模型轉(zhuǎn)字典,這里并沒有調(diào)用蔫巩,所以我們到后面再說:
- (NSMutableDictionary *)mj_keyValues {
return [self mj_keyValuesWithKeys:nil ignoredKeys:nil];
}
然后我們step out
往后進(jìn)入核心代碼:
- (instancetype)mj_setKeyValues:(id)keyValues
{
return [self mj_setKeyValues:keyValues context:nil];
}
/**
核心代碼:
*/
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得解析后的JSON對(duì)象
// 在這里其實(shí)沒做什么
keyValues = [keyValues mj_JSONObject];
// 類型判斷
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個(gè)字典");
Class clazz = [self class];
// 獲取當(dāng)前類及所有父類的白名單屬性
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
// 獲取當(dāng)前類及所有父類的黑名單屬性
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
//通過封裝的方法回調(diào)一個(gè)通過運(yùn)行時(shí)編寫的谆棱,用于返回屬性列表的方法。
// 遍歷每一個(gè)屬性名
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 核心轉(zhuǎn)換的block批幌,暫時(shí)缺省
······
}];
// 轉(zhuǎn)換完畢
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
[self mj_keyValuesDidFinishConvertingToObject];
}
return self;
}
- 注釋都在代碼中了础锐,核心邏輯就在這里完成,主要就是保證屬性和值類型一致荧缘,否則賦值為nil。
- 由于block是異步執(zhí)行的拦宣,從業(yè)務(wù)邏輯上來說block是在
+mj_enumerateProperties
方法之后執(zhí)行截粗。所以這里暫時(shí)缺省,放到后面說鸵隧。 -
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {}
在這個(gè)方法中利用runtime
獲取當(dāng)前模型類的所有屬性绸罗,并將屬性的** 類型,名字,類型編碼**等分別拆開。用內(nèi)部模型MJProperty
保存這些變量豆瘫。然后在block中對(duì)每一個(gè)屬性進(jìn)行轉(zhuǎn)換珊蟀。 - 說一說遍歷吧。這里MJ自己寫了一個(gè)
enumerate
,其實(shí)Foundation
中NSArray
,NSDictionary
以及NSSet
都有enumerateObjectsUsingBlock
方法(實(shí)際名稱有所差別)外驱。如果對(duì)index
不感興趣育灸,使用自帶的遍歷方法能使代碼更加清晰。
來看看+mj_enumerateProperties
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration {
// 獲得本類及父類所有屬性,并包裝成MJProperty對(duì)象
NSArray *cachedProperties = [self properties];
// 遍歷每一個(gè)包裝的屬性,在block中進(jìn)行類型轉(zhuǎn)換并賦值
BOOL stop = NO;
for (MJProperty *property in cachedProperties) {
enumeration(property, &stop);
if (stop) break;
}
}
沒什么說的昵宇,繼續(xù)深入:
/**
runtime獲取當(dāng)前類的屬性,轉(zhuǎn)換成MJProperty對(duì)象保存到數(shù)組磅崭,再將數(shù)組關(guān)聯(lián)到本類
*/
+ (NSMutableArray *)properties {
// 嘗試獲取已經(jīng)包裝過的屬性
NSMutableArray *cachedProperties = [self dictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
// 沒有存儲(chǔ)過屬性
if (cachedProperties == nil) {
cachedProperties = [NSMutableArray array];
// 遍歷本類及所有父類,獲取所有屬性并拆解包裝成MJProperty對(duì)象
[self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1.獲得所有的成員變量
unsigned int outCount = 0;
// 獲取當(dāng)前類的屬性列表
objc_property_t *properties = class_copyPropertyList(c, &outCount);
// 2.遍歷每一個(gè)成員變量,并打包成一個(gè)MJProperty對(duì)象
for (unsigned int i = 0; i<outCount; i++) {
// 將屬性保存為MJProperty對(duì)象
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
// 如果屬性所在的類不是自定義模型類就進(jìn)行下次循環(huán)
if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
// 設(shè)置屬性所在的模型類
property.srcClass = c;
// 處理多級(jí)映射及替換
[property setOriginKey:[self propertyKey:property.name] forClass:self];
// 處理數(shù)組中的自定義類型
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
// 保存對(duì)象化的屬性
[cachedProperties addObject:property];
}
// 3.釋放內(nèi)存
free(properties);
}];
// 就是賦值瓦哎,將存儲(chǔ)屬性(MJProperty)的數(shù)組根據(jù)對(duì)應(yīng)key保存
[self dictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
}
return cachedProperties;
}
-
MJCachedPropertiesKey
也是一個(gè)全局key砸喻,對(duì)應(yīng)一個(gè)字典柔逼,該字典的key是本類類名,value是保存包裝過的屬性的數(shù)組割岛。 -
objc_property_t *properties = class_copyPropertyList(c, &outCount)
聲明一個(gè)objc_property_t
類型的指針變量愉适,指向一個(gè)同類型的動(dòng)態(tài)分配的指針數(shù)組。因此所有class_copy系列使用之后癣漆,都要記得free维咸。
靜態(tài)指針與動(dòng)態(tài)指針的free
接下來進(jìn)入:
+ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration {
// 1.沒有block就直接返回
if (enumeration == nil) return;
// 2.停止遍歷的標(biāo)記
BOOL stop = NO;
// 3.當(dāng)前正在遍歷的類
Class c = self;
// 4.開始遍歷每一個(gè)類
while (c && !stop) {
// 4.1.執(zhí)行操作
enumeration(c, &stop);
// 4.2.獲得父類
c = class_getSuperclass(c);
// 4.3.如果是OC類,結(jié)束循環(huán)
if ([MJFoundation isClassFromFoundation:c]) break;
}
}
- 又是一個(gè)while循環(huán)獲取父類扑媚。不同的是多了最后一個(gè)Foundation類判斷腰湾。
MJFoundation.m
+ (NSSet *)foundationClasses {
if (foundationClasses_ == nil) {
// 集合中沒有NSObject,因?yàn)閹缀跛械念惗际抢^承自NSObject疆股,具體是不是NSObject需要特殊判斷
foundationClasses_ = [NSSet setWithObjects:
[NSURL class],
[NSDate class],
[NSValue class],
[NSData class],
[NSError class],
[NSArray class],
[NSDictionary class],
[NSString class],
[NSAttributedString class], nil];
}
return foundationClasses_;
}
/**
* 判斷類型是Foundation類還是自定義類
*/
+ (BOOL)isClassFromFoundation:(Class)c {
if (c == [NSObject class] || c == [NSManagedObject class]) return YES;
__block BOOL result = NO;
[[self foundationClasses] enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) {
if ([c isSubclassOfClass:foundationClass]) {
result = YES;
// 結(jié)束遍歷
*stop = YES;
}
}];
return result;
}
- 這個(gè)方法的作用是為了得到自定義的模型類
-
+isSubclassOfClass
判斷兩個(gè)類是否是父子關(guān)系或相等费坊。 - 由于很多類都是NSObject的子類,所以排除
NSObject
需要單獨(dú)判斷
接下來看看如何將獲取到的屬性拆解并包裝成MJProperty對(duì)象:
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property {
// 當(dāng)前屬性是否已經(jīng)緩存過
// 若是直接返回
// 若否進(jìn)行拆解
MJProperty *propertyObj = objc_getAssociatedObject(self, property);
if (propertyObj == nil) {
propertyObj = [[self alloc] init];
propertyObj.property = property;
// 呼應(yīng)上面的objc_get
objc_setAssociatedObject(self, property, propertyObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return propertyObj;
}
// set方法
- (void)setProperty:(objc_property_t)property
{
_property = property;
MJExtensionAssertParamNotNil(property);
// 1.屬性名
_name = @(property_getName(property));
// 2. 對(duì)屬性字符串截取出OC使用的類型
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)];
}
// 3.屬性類型:包裝成MJPropertyType對(duì)象
_type = [MJPropertyType cachedTypeWithCode:code];
}
-
property_getAttributes
獲取的屬性字符串是經(jīng)過OC類型編碼的
比如@property (nonatomic, copy) NSString *name;
編碼@"T@\"NSString\",&,N,V_name"
T@\"NSString\"
:表示NSString類型
C
:表示copy關(guān)鍵字
N
:表示nonatomic關(guān)鍵字
V_name
:表示屬性名name - 屬性編碼一定是
T
開頭后面緊跟類型編碼旬痹,屬性名以V
開頭后面緊跟屬性名或?qū)嵗兞棵?有_
)附井,然后各部分以逗號(hào)分隔。屬性字符串是含轉(zhuǎn)義字符的两残,使用p
命令可以看到永毅。 - 詳情查看開發(fā)者官網(wǎng)Property Type String和Type Coding
- 以上面name為例,截取后值剩下
@\"NSString\"
。只需要再截取出NSString
字符串人弓,然后利用反射機(jī)制就能得到NSString
類型沼死。
對(duì)于屬性類型,MJ 又包裝成了MJPropertyType
對(duì)象:
+ (instancetype)cachedTypeWithCode:(NSString *)code {
MJExtensionAssertParamNotNil2(code, nil);
@synchronized (self) {
// 當(dāng)前類型是否已經(jīng)緩存過
// 若是直接返回
// 若否進(jìn)行包裝
MJPropertyType *type = types_[code];
if (type == nil) {
type = [[self alloc] init];
type.code = code;
types_[code] = type;
}
return type;
}
}
- (void)setCode:(NSString *)code
{
_code = code;
MJExtensionAssertParamNotNil(code);
if ([code isEqualToString:MJPropertyTypeId]) {
// id 類型
_idType = YES;
} else if (code.length == 0) {
_KVCDisabled = YES;
} else if (code.length > 3 && [code hasPrefix:@"@\""]) {
// 去掉 @\" 和 " 崔赌,截取中間的類型名稱
_code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
// 反射機(jī)制
_typeClass = NSClassFromString(_code);
// 對(duì)象類型是否屬于Foundation
_fromFoundation = [MJFoundation isClassFromFoundation:_typeClass];
// 是否是NSNumber對(duì)象
_numberType = [_typeClass isSubclassOfClass:[NSNumber class]];
} else if ([code isEqualToString:MJPropertyTypeSEL] ||
[code isEqualToString:MJPropertyTypeIvar] ||
[code isEqualToString:MJPropertyTypeMethod]) {
// SEL類型意蛀,成員變量,IMP類型
_KVCDisabled = YES;
}
// 基本數(shù)據(jù)類型
NSString *lowerCode = _code.lowercaseString;
NSArray *numberTypes = @[MJPropertyTypeInt, MJPropertyTypeShort, MJPropertyTypeBOOL1, MJPropertyTypeBOOL2, MJPropertyTypeFloat, MJPropertyTypeDouble, MJPropertyTypeLong, MJPropertyTypeLongLong, MJPropertyTypeChar];
if ([numberTypes containsObject:lowerCode]) {
_numberType = YES;
if ([lowerCode isEqualToString:MJPropertyTypeBOOL1]
|| [lowerCode isEqualToString:MJPropertyTypeBOOL2]) {
_boolType = YES;
}
}
}
- 判斷具體類型健芭,對(duì)象類型利用反射機(jī)制形成县钥,基本數(shù)據(jù)類型不能使用反射機(jī)制,所以采用獨(dú)立標(biāo)識(shí)
-
@synchronized
同步鎖慈迈,用于多線程操作時(shí)防止多個(gè)線程對(duì)同一個(gè)對(duì)象寫入若贮。但是這里為什么使用,筆者沒搞清楚痒留。如果有人知道請(qǐng)留言告知谴麦。(是因?yàn)閎lock是異步執(zhí)行的嗎?) - 之所以會(huì)有
_KVCDisabled
屬性狭瞎,是因?yàn)?KVC
的value
不支持基礎(chǔ)數(shù)據(jù)類型细移。基礎(chǔ)數(shù)據(jù)類型需使用NSNumber
包裝熊锭。
至此對(duì)屬性的拆解和重新包裝就完成了弧轧,接下來回到+ (NSMutableArray *)properties
方法雪侥,看看多級(jí)映射以及屬性和json數(shù)據(jù)key不一樣時(shí)的處理。
來看調(diào)用:
[property setOriginKey:[self propertyKey:property.name] forClass:self];
-
+ propertyKey
方法是根據(jù)當(dāng)前屬性名獲取json數(shù)據(jù)中要替換的keyPath精绎,如果不需要替換速缨,則直接返回該屬性名。內(nèi)部處理類同開頭介紹的白名單獲取方式代乃。 - 需要注意的是獲取方式有兩個(gè)方法
replacedKeyFromPropertyName121
和replacedKeyFromPropertyName
旬牲。 - 區(qū)別
前者采用objc_setAssociatedObject
關(guān)聯(lián)block和對(duì)應(yīng)的key時(shí),使用的策略是OBJC_ASSOCIATION_RETAIN_NONATOMIC
,而后者采用的是OBJC_ASSOCIATION_COPY_NONATOMIC
搁吓。在ARC下原茅,一般沒什么差別,因?yàn)橄到y(tǒng)默認(rèn)對(duì)block采用copy操作堕仔。而在MRC下擂橘,block中如果使用了外部變量,block會(huì)存在于棧中摩骨。棧中的對(duì)象釋放由系統(tǒng)控制通贞,所以很可能在block使用前,系統(tǒng)就銷毀了對(duì)象恼五。此時(shí)再使用就可能crash昌罩。因此聲明block時(shí),最好使用copy灾馒,復(fù)制到堆中茎用,由程序員控制對(duì)象的釋放。
參考鏈接:iOS 非ARC下的block - 因此建議使用
replacedKeyFromPropertyName121
協(xié)議或block睬罗。
/**
將要替換的json的keyPath拆解并持有保存
@param originKey json數(shù)據(jù)中要替換的keyPath绘搞,若不需替換則為屬性本身的名稱
@param c 當(dāng)前類
*/
- (void)setOriginKey:(id)originKey forClass:(Class)c
{
// 字符串類型的key:可能是不需替換的或者是json中的keyPath
if ([originKey isKindOfClass:[NSString class]]) {
// 拆解keyPath并保存到數(shù)組
NSArray *propertyKeys = [self propertyKeysWithStringKey:originKey];
if (propertyKeys.count) {
[self setPorpertyKeys:@[propertyKeys] forClass:c];
}
} else if ([originKey isKindOfClass:[NSArray class]]) {
// 什么情況是數(shù)組?一個(gè)屬性可能對(duì)應(yīng)多個(gè)json keyPath
// 每一個(gè)keyPath都要拆解包裝并保存在一個(gè)數(shù)組
// 再將每一數(shù)組按順序保存到一個(gè)數(shù)組
NSMutableArray *keyses = [NSMutableArray array];
for (NSString *stringKey in originKey) {
NSArray *propertyKeys = [self propertyKeysWithStringKey:stringKey];
if (propertyKeys.count) {
[keyses addObject:propertyKeys];
}
}
if (keyses.count) {
[self setPorpertyKeys:keyses forClass:c];
}
}
}
/**
* 拆解多級(jí)映射的keyPath(包括數(shù)組的索引值)傅物,并包裝成MJPropertyKey對(duì)象,存儲(chǔ)到數(shù)組中
*/
- (NSArray *)propertyKeysWithStringKey:(NSString *)stringKey
{
if (stringKey.length == 0) return nil;
NSMutableArray *propertyKeys = [NSMutableArray array];
// 如果有多級(jí)映射
// 模型屬性對(duì)應(yīng)json中第n級(jí)key
// 處理json中的數(shù)組對(duì)象級(jí)
// 要求寫keyPath時(shí)采用點(diǎn)語法的形式
NSArray *oldKeys = [stringKey componentsSeparatedByString:@"."];
// 遍歷每一個(gè)path
for (NSString *oldKey in oldKeys) {
NSUInteger start = [oldKey rangeOfString:@"["].location;
if (start != NSNotFound) {
// 1. 處理數(shù)組項(xiàng)
// 裁減出數(shù)組名
NSString *prefixKey = [oldKey substringToIndex:start];
NSString *indexKey = prefixKey;
if (prefixKey.length) {
MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init];
propertyKey.name = prefixKey;
[propertyKeys addObject:propertyKey];
// 裁剪出索引值
indexKey = [oldKey stringByReplacingOccurrencesOfString:prefixKey withString:@""];
}
/** 解析索引 **/
NSArray *cmps = [[indexKey stringByReplacingOccurrencesOfString:@"[" withString:@""] componentsSeparatedByString:@"]"];
// cmps數(shù)組中只有索引值字符串和一個(gè)空字符串
for (NSInteger i = 0; i<cmps.count - 1; i++) {
MJPropertyKey *subPropertyKey = [[MJPropertyKey alloc] init];
subPropertyKey.type = MJPropertyKeyTypeArray;
subPropertyKey.name = cmps[i];
[propertyKeys addObject:subPropertyKey];
}
} else {
// 沒有索引的json key部分或者是本來不需替換的json key
MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init];
propertyKey.name = oldKey;
[propertyKeys addObject:propertyKey];
}
}
return propertyKeys;
}
- (void)setPorpertyKeys:(NSArray *)propertyKeys forClass:(Class)c
{
if (propertyKeys.count == 0) return;
self.propertyKeysDict[NSStringFromClass(c)] = propertyKeys;
}
- 使用
MJPropertyKey
對(duì)象來包裝多級(jí)映射的每一級(jí)path琉预。每個(gè)對(duì)象都用一個(gè)枚舉類型屬性表明它的使用方式董饰。
typedef enum {
// 表明當(dāng)前對(duì)象的值當(dāng)做字典的key
MJPropertyKeyTypeDictionary = 0,
// 表明當(dāng)前對(duì)象的值當(dāng)做數(shù)組的索引值使用
MJPropertyKeyTypeArray
} MJPropertyKeyType;
- 包裝MJPropertyKey對(duì)象時(shí)有一個(gè)細(xì)節(jié):只有包裝索引時(shí)設(shè)置了它的
MJPropertyKeyType
為MJPropertyKeyTypeArray
。而卻沒有看到設(shè)置MJPropertyKeyTypeDictionary
類型的地方圆米。
因?yàn)槊杜e類型的屬性卒暂,如果不設(shè)置,會(huì)默認(rèn)設(shè)置成枚舉的第一個(gè)成員娄帖,這里即是默認(rèn)設(shè)置成MJPropertyKeyTypeDictionary
也祠。 - 至于屬性和json key不一致,需要替換的情況近速。就將json key看做只有一個(gè)path的keyPath诈嘿,仍然采用上面的方法堪旧。
-
MJProperty
中聲明了一個(gè)propertyKeysDict
屬性,以模型類名為key奖亚,value為MJPropertyKey
對(duì)象的數(shù)組淳梦,用來保存每一級(jí)path
看完多級(jí)映射,現(xiàn)在再回到+ (NSMutableArray *)properties
方法昔字,看MJ又如何處理數(shù)組中的模型, 先看調(diào)用:
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
-
+ propertyObjectClassInArray
也是比照白名單獲取方式分析爆袍。這里是根據(jù)屬性名獲取json中對(duì)應(yīng)的數(shù)組的key。
- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c
{
if (!objectClass) return;
self.objectClassInArrayDict[NSStringFromClass(c)] = objectClass;
}
- 數(shù)組中的模型處理要簡(jiǎn)單的多作郭,直接保存在字典中陨囊,key仍然是外部模型類的類名。
-
objectClassInArrayDict
是MJProperty
的一個(gè)字典屬性夹攒,key是模型類名蜘醋,value則是數(shù)組中的模型類型。
接下來我們來看上面核心代碼中缺省的block:
// 遍歷每一個(gè)屬性:包裝成了MJProperty對(duì)象
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 0.檢測(cè)是否被忽略
// 屬性不在白名單中
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 屬性在黑名單中
if ([ignoredPropertyNames containsObject:property.name]) return;
// 1.取出屬性值
id value;
// 獲取拆解json keyPath得到的數(shù)組芹助,每個(gè)path包裝成了MJPropertyKey對(duì)象
// 內(nèi)部就是MJProperty中的propertyKeysDict字典屬性
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
// json對(duì)象
value = keyValues;
// propertyKeys: 保存一個(gè)被拆解的json keyPath
// propertyKey : json keyPath 中的一個(gè)path
for (MJPropertyKey *propertyKey in propertyKeys) {
// kvc 從json字典中取值
// 如果是多級(jí)映射堂湖,每次返回的值為下一級(jí)對(duì)象,直到需要的值
value = [propertyKey valueInObject:value];
}
if (value) break;
}
/**
* json數(shù)據(jù)中獲得的值
* 1. 有自定義的值處理方式状土,執(zhí)行自定義
* 2. 無自定義方式无蜂,則將數(shù)據(jù)的類型轉(zhuǎn)換為屬性的類型賦值(保持類型一致)
*/
// 值的進(jìn)一步處理如將字符串處理成NSURL或NSDate,如果不需處理則直接返回原值
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有過濾后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果沒有值,就直接返回
if (!value || value == [NSNull null]) return;
// 2.復(fù)雜處理 -- 處理模型中的模型和數(shù)組中的模型
// 屬性的類型
MJPropertyType *type = property.type;
// 屬性的類型所屬的類,基本數(shù)據(jù)類型則為nil
Class propertyClass = type.typeClass;
// 獲取數(shù)組中的自定義類型,內(nèi)部就是一個(gè)字典取值
Class objectClass = [property objectClassInArrayForClass:[self class]];
// (屬性)不可變 -> 可變處理(獲取的值)
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
value = [NSMutableData dataWithData:value];
}
if (!type.isFromFoundation && propertyClass) {
// 自定義的模型類,此處是處理模型中的模型
// 進(jìn)一步的json轉(zhuǎn)模型
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
// 數(shù)組中的模型
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// 數(shù)組中的模型是NSURL
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
}
value = urlArray;
} else {
// 處理數(shù)組中嵌套模型蒙谓,返回的數(shù)組中是json轉(zhuǎn)model過的
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else {
if (propertyClass == [NSString class]) {
// 屬性類型是NSString斥季,值的類型是NSNumber或NSURL
if ([value isKindOfClass:[NSNumber class]]) {
// NSNumber -> NSString
value = [value description];
} else if ([value isKindOfClass:[NSURL class]]) {
// NSURL -> NSString
value = [value absoluteString];
}
} else if ([value isKindOfClass:[NSString class]]) {
// 屬性類型是基本數(shù)據(jù)類型或者NSURL,但是對(duì)應(yīng)的json的值是字符串類型
if (propertyClass == [NSURL class]) {
// NSString -> NSURL
// url字符串轉(zhuǎn)碼
value = [value mj_url];
// 基本數(shù)據(jù)類型不能使用反射機(jī)制獲取累驮,通過設(shè)置標(biāo)識(shí)來確定
} else if (type.isNumberType) {
// 數(shù)字是以字符串形式呈現(xiàn)的
NSString *oldValue = value;
if (type.typeClass == [NSDecimalNumber class]) {
// 浮點(diǎn)數(shù)類酣倾,提供更精確的浮點(diǎn)數(shù)計(jì)算方式,不可變類
value = [NSDecimalNumber decimalNumberWithString:oldValue];
} else {
// 字符串轉(zhuǎn)換成數(shù)組
value = [numberFormatter_ numberFromString:oldValue];
}
// json中的布爾類型數(shù)據(jù)是字符串顯示的
if (type.isBoolType) {
// 字符串轉(zhuǎn)BOOL(字符串沒有charValue方法)
// 系統(tǒng)會(huì)調(diào)用字符串的charValue轉(zhuǎn)為BOOL類型
NSString *lower = [oldValue lowercaseString];
if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
value = @YES;
} else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
value = @NO;
}
}
}
}
// value和property類型不匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
}
// 3.賦值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
+mj_getNewValueFromObject
方法用于對(duì)值的進(jìn)一步處理谤专。內(nèi)部邏輯和白名單獲取方式類似,這里也不再分析躁锡。-
異常捕獲--詳情可以戳這個(gè)鏈接@_超
@try { // 可能會(huì)發(fā)生異常的代碼 } @catch (NSException *exception) { // 捕獲異常后的處理 } @try中的代碼塊如果發(fā)生異常,就會(huì)被catch捕獲并形成NSException對(duì)象置侍,可以使用該對(duì)象的reason或userInfo查看原因映之。如果沒有異常則不執(zhí)行catch。此外還有一個(gè)@finally(貌似很少用)蜡坊, 不管是否異常都會(huì)執(zhí)行杠输,即使@try或@catch中有return語句。
模型中的模型的處理方式就是使用新模型類將整個(gè)流程再走一次秕衙。
來看看如何從json數(shù)據(jù)取值
- (id)valueInObject:(id)object
{
if ([object isKindOfClass:[NSDictionary class]] && self.type == MJPropertyKeyTypeDictionary) {
// 從json字典中根據(jù)原始key取值
return object[self.name];
} else if ([object isKindOfClass:[NSArray class]] && self.type == MJPropertyKeyTypeArray) {
// json中的數(shù)組對(duì)象蠢甲,index即保存過的索引
NSArray *array = object;
NSUInteger index = self.name.intValue;
if (index < array.count) return array[index];
return nil;
}
return nil;
}
接下來就是處理數(shù)組中的模型:
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context
{
// 如果是JSON字符串,新模型的元數(shù)據(jù)是JSON字符串
keyValuesArray = [keyValuesArray mj_JSONObject];
// 1.判斷真實(shí)性
MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray參數(shù)不是一個(gè)數(shù)組");
// 如果數(shù)組里面放的是Foundation類的數(shù)據(jù)
if ([MJFoundation isClassFromFoundation:self])
return [NSMutableArray arrayWithArray:keyValuesArray];
// 2.創(chuàng)建數(shù)組保存轉(zhuǎn)換后的模型
NSMutableArray *modelArray = [NSMutableArray array];
// 3.遍歷數(shù)組中的每個(gè)模型的元數(shù)據(jù)
for (NSDictionary *keyValues in keyValuesArray) {
if ([keyValues isKindOfClass:[NSArray class]]){
// 數(shù)組當(dāng)中還有模型据忘,遞歸
[modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];
} else {
// 對(duì)數(shù)組中嵌套的模型進(jìn)行json-model轉(zhuǎn)換
id model = [self mj_objectWithKeyValues:keyValues context:context];
if (model) [modelArray addObject:model];
}
}
return modelArray;
}
然后就是保證屬性的類型和值的類型保持一致鹦牛,最后通過KVC方式為模型類屬性賦值搞糕,整個(gè)轉(zhuǎn)換就完成了。