MJExtension是一款開源的鸵隧,簡(jiǎn)單易用的字典與模型轉(zhuǎn)換框架拗馒。
常用的方法谣膳,主要是以下幾個(gè):
// JSON|字典 轉(zhuǎn) 模型
+ (instancetype)mj_objectWithKeyValues:(id)keyValues;
// 通過(guò) JSON|字典 為 模型賦值
- (instancetype)mj_setKeyValues:(id)keyValues;
// 模型轉(zhuǎn)JSON
- (NSMutableDictionary *)mj_keyValues;
// JSON數(shù)組轉(zhuǎn)模型數(shù)組
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray;
功能
JSON|字典 轉(zhuǎn) 模型
+ mj_objectWithKeyValues:
+ mj_objectWithKeyValues:
是框架中最簡(jiǎn)單的JSON轉(zhuǎn)模型的方法荷鼠,通過(guò)直接調(diào)用類方法并傳入JSON數(shù)據(jù)即可快速實(shí)現(xiàn)轉(zhuǎn)換窖铡。而在+ mj_objectWithKeyValues:
方法中,實(shí)際是調(diào)用了+ mj_objectWithKeyValues: context:
方法衡载,參數(shù)中如果傳了contenxt
搔耕,最終會(huì)返回CoreData模型;如果不傳痰娱,返回已賦值的模型弃榨。
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得JSON對(duì)象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues參數(shù)不是一個(gè)字典");
// 判斷是否傳入 "contenxt" 參數(shù)
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];
}
在為實(shí)例賦值的方法中,由于- mj_setKeyValues:
實(shí)際的實(shí)現(xiàn)是調(diào)用- mj_setKeyValues: context:
猜揪。所以我們直接進(jìn)入- mj_setKeyValues: context:
進(jìn)行分析惭墓。
首先坛梁,需要將傳入的keyValues
處理成可用的JSON對(duì)象而姐,并獲取當(dāng)前類的類型,以及黑白名單屬性划咐。
// 獲得JSON對(duì)象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個(gè)字典");
Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
緊接著拴念,調(diào)用類的擴(kuò)展方法+ mj_enumerateProperties:
,獲取和遍歷類的屬性列表褐缠,通過(guò)block參數(shù)進(jìn)行回調(diào)政鼠,在回調(diào)的代碼塊中,對(duì)每個(gè)屬性進(jìn)行注意賦值队魏。
核心代碼:
//通過(guò)封裝的方法回調(diào)一個(gè)通過(guò)運(yùn)行時(shí)編寫的公般,用于返回屬性列表的方法万搔。
[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;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的過(guò)濾
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有過(guò)濾后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果沒(méi)有值,就直接返回
if (!value || value == [NSNull null]) return;
// 2.復(fù)雜處理
MJPropertyType *type = property.type; // 數(shù)據(jù)類型類
Class propertyClass = type.typeClass; // 對(duì)象類型
Class objectClass = [property objectClassInArrayForClass:[self class]]; // 數(shù)組中的模型類型
// 不可變 -> 可變處理
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) { // 模型屬性
// 既不是基礎(chǔ)類型官帘,也不是NS類型瞬雹。即:基本數(shù)據(jù)類型
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// 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ù)組
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else if (propertyClass == [NSString class]) {
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]]) {
if (propertyClass == [NSURL class]) {
// NSString -> NSURL
// 字符串轉(zhuǎn)碼
value = [value mj_url];
} else if (type.isNumberType) {
NSString *oldValue = value;
// NSString -> NSDecimalNumber, 使用 DecimalNumber 來(lái)轉(zhuǎn)換數(shù)字, 避免丟失精度以及溢出
NSDecimalNumber *decimalValue = [NSDecimalNumber decimalNumberWithString:oldValue
locale:numberLocale];
// 檢查特殊情況
if (decimalValue == NSDecimalNumber.notANumber) {
value = @(0);
}else if (propertyClass != [NSDecimalNumber class]) {
value = [decimalValue mj_standardValueWithTypeCode:type.code];
} else {
value = decimalValue;
}
// 如果是BOOL
if (type.isBoolType) {
// 字符串轉(zhuǎn)BOOL(字符串沒(méi)有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;
}
}
}
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
// 過(guò)濾 NSDecimalNumber類型
if (![value isKindOfClass:[NSDecimalNumber class]]) {
value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
}
}
// 經(jīng)過(guò)轉(zhuǎn)換后, 最終檢查 value 與 property 是否匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
// 3.賦值(KVC)
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
從代碼中,可以直觀看出刽虹,賦值操作主要分為步驟4個(gè)步驟酗捌。
0.檢測(cè)是否被忽略
// 白名單
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 黑名單
if ([ignoredPropertyNames containsObject:property.name]) return;
判斷黑白名單中是否包含相應(yīng)的屬性名稱。
1.取出屬性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的過(guò)濾
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有過(guò)濾后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果沒(méi)有值涌哲,就直接返回
if (!value || value == [NSNull null]) return;
因?yàn)橥粋€(gè)成員屬性胖缤,父類和子類的行為可能不一致(originKey、propertyKeys阀圾、objectClassInArray)哪廓,所以其鍵值可能是一個(gè)數(shù)組,通過(guò)循環(huán)這個(gè)數(shù)組嘗試獲取值稍刀。
對(duì)值得過(guò)濾撩独,指的是使用者通過(guò)實(shí)現(xiàn)- (id)mj_newValueFromOldValue: property:
方法,對(duì)結(jié)果進(jìn)行進(jìn)一步的處理(比如字符串日期處理為NSDate账月、字符串nil處理為@"")综膀。
2.復(fù)雜處理
MJPropertyType *type = property.type; // 數(shù)據(jù)類型的信息
Class propertyClass = type.typeClass; // 屬性的類型
Class objectClass = [property objectClassInArrayForClass:[self class]]; // 數(shù)組中模型的類型
// 不可變 -> 可變處理
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) { // 模型屬性
// 既不是基礎(chǔ)類型,也不是NS類型局齿。即:基本數(shù)據(jù)類型
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// 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ù)組
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else if (propertyClass == [NSString class]) {
} else if ([value isKindOfClass:[NSString class]]) {
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
}
復(fù)雜處理剧劝,主要是對(duì)屬性值的類型進(jìn)行判斷,屬性值的類型只要分為:模型屬性(自定義類)抓歼、數(shù)組屬性和其他屬性(NS類型)讥此。
模型屬性的value,需要通過(guò)繼續(xù)調(diào)用- mj_objectWithKeyValues:value context:
方法谣妻,將字典轉(zhuǎn)換成模型萄喳。
數(shù)組屬性的值,則需要根據(jù)數(shù)組中模型的類型蹋半,進(jìn)行循環(huán)轉(zhuǎn)換他巨。
其他情況的值,可以通過(guò)簡(jiǎn)單的轉(zhuǎn)化或者直接使用减江。
3.賦值
至此染突,屬性信息和值都有了。
// 經(jīng)過(guò)轉(zhuǎn)換后, 最終檢查 value 與 property 是否匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
[property setValue:value forObject:self];
在確定 value的值 與 property得類型 確實(shí)匹配后辈灼,通過(guò)KVC進(jìn)行賦值份企。
/**
* 設(shè)置成員變量的值
*/
- (void)setValue:(id)value forObject:(id)object
{
if (self.type.KVCDisabled || value == nil) return;
[object setValue:value forKey:self.name];
}
到此,JSON轉(zhuǎn)模型的工作就完成了巡莹。
模型 轉(zhuǎn) JSON|字典
- mj_keyValues
模型轉(zhuǎn)JSON的方法主要有:
// 轉(zhuǎn)換并返回模型中所有屬性的鍵值對(duì)
- (NSMutableDictionary *)mj_keyValues;
/**
@para keys 需要返回的特定鍵的數(shù)組
@return 特定關(guān)鍵詞的鍵值對(duì)
*/
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys;
/**
@para ignoredKeys 需要忽略的特定鍵的數(shù)組
@return 除特定關(guān)鍵詞的其他有效鍵值對(duì)
*/
- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys;
以上方法統(tǒng)一調(diào)用了- mj_keyValuesWithKeys:ignoredKeys:
司志,讓我們直接進(jìn)入這個(gè)方法一探究竟甜紫。
- mj_keyValuesWithKeys:ignoredKeys:
方法與JSON轉(zhuǎn)模型的核心邏輯是極其相似的,即通過(guò)遍歷類的所有屬性骂远,進(jìn)行相關(guān)操作棵介,這里我們直接進(jìn)入代碼塊,進(jìn)行分析吧史。
0.檢測(cè)是否被忽略
// 白名單
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 黑名單
if ([ignoredPropertyNames containsObject:property.name]) return;
// 只需要返回的特定鍵
if (keys.count && ![keys containsObject:property.name]) return;
// 需要被忽略的特定鍵
if ([ignoredKeys containsObject:property.name]) return;
返回的結(jié)果邮辽,不僅可以對(duì)黑白名單中的屬性進(jìn)行篩選,還可以根據(jù)具體場(chǎng)景設(shè)置需要返回和忽略的特定鍵值贸营。
1.取出屬性值
使用KVC取值吨述。
id value = [property valueForObject:self];
/**
* 獲得成員變量的值
*/
- (id)valueForObject:(id)object
{
if (self.type.KVCDisabled) return [NSNull null];
id value = [object valueForKey:self.name];
// 32位BOOL類型轉(zhuǎn)換json后成Int類型
/** https://github.com/CoderMJLee/MJExtension/issues/545 */
// 32 bit device OR 32 bit Simulator
#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__)
if (self.type.isBoolType) {
value = @([(NSNumber *)value boolValue]);
}
#endif
return value;
}
2.模型屬性和數(shù)組的處理
如果當(dāng)前的屬性屬于模型類型或數(shù)組,則需要對(duì) value
進(jìn)行遞歸調(diào)用 - mj_keyValues
方法钞脂,直至最終得到非模型和非數(shù)組的數(shù)據(jù)類型揣云。
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
if (!type.isFromFoundation && propertyClass) {
value = [value mj_keyValues];
} else if ([value isKindOfClass:[NSArray class]]) {
// 3.處理數(shù)組里面有模型的情況
value = [NSObject mj_keyValuesArrayWithObjectArray:value];
} else if (propertyClass == [NSURL class]) {
value = [value absoluteString];
}
3.賦值
在對(duì)結(jié)果keyValues
進(jìn)行賦值之前,需要先判斷創(chuàng)建鍵值時(shí)冰啃,是否引用了替換鍵 —— 也就是在+ mj_replacedKeyFromPropertyName
方法中返回的自定義映射表邓夕。
對(duì)于沒(méi)有引用替換鍵的值,可以直接賦值阎毅。
keyValues[property.name] = value;
對(duì)于引用了替換鍵的值焚刚,需要獲取原始的key,最終結(jié)果也將返回最原始的JSON或字典扇调。
// 獲取原始key
NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
NSUInteger keyCount = propertyKeys.count;
// 創(chuàng)建字典
__block id innerContainer = keyValues;
[propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
// 下一個(gè)屬性
MJPropertyKey *nextPropertyKey = nil;
if (idx != keyCount - 1) {
nextPropertyKey = propertyKeys[idx + 1];
}
if (nextPropertyKey) { // 不是最后一個(gè)key
// 當(dāng)前propertyKey對(duì)應(yīng)的字典或者數(shù)組
id tempInnerContainer = [propertyKey valueInObject:innerContainer];
if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) {
if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
tempInnerContainer = [NSMutableDictionary dictionary];
} else {
tempInnerContainer = [NSMutableArray array];
}
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = tempInnerContainer;
} else {
innerContainer[propertyKey.name.intValue] = tempInnerContainer;
}
}
if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) {
NSMutableArray *tempInnerContainerArray = tempInnerContainer;
int index = nextPropertyKey.name.intValue;
while (tempInnerContainerArray.count < index + 1) {
[tempInnerContainerArray addObject:[NSNull null]];
}
}
innerContainer = tempInnerContainer;
} else { // 最后一個(gè)key
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = value;
} else {
innerContainer[propertyKey.name.intValue] = value;
}
}
}];
總結(jié)
核心代碼:
- JSON|字典轉(zhuǎn)模型的各類方法矿咕,最終都會(huì)調(diào)用
- mj_setKeyValues:(id)keyValues context:
- 模型轉(zhuǎn)JSON|字典的各類方法,最終都會(huì)調(diào)用
- (NSMutableDictionary *)mj_keyValuesWithKeys: ignoredKeys:
性能方面:
- 使用runtime動(dòng)態(tài)生成類的屬性信息狼钮,并通過(guò)緩存機(jī)制進(jìn)行性能提優(yōu)碳柱。
容錯(cuò)方面
- 在JSON|字典轉(zhuǎn)模型最后賦值之前,會(huì)對(duì)值和屬性的類型進(jìn)行一致性的判斷熬芜。如果不匹配莲镣,
value
會(huì)被置為nil,避免潛在的Crash風(fēng)險(xiǎn)涎拉。