一、初始
其实苹果爸爸也给我们提供了字典转模型的方法(如下图),就是容错性不高。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues
具体用法:
NSDictionary *dic = @{@"name":@"zht",@"age":@27,@"job":@"coder" };
UserModel *model = [[UserModel alloc] init];
[model setValuesForKeysWithDictionary:dic];
注意
:这里必须model的属性要包含dic中的key,要不就会出现如下奔溃。
意思也好理解,标示这个model没有对应的属性。这时model就要调用setValue:forUndefinedKey:方法,但是model类没有实现这个方法。解决方案:在model类中实现setValue:forUndefinedKey:方法。
二、基本用法
相比原生,MJExtension的功能还是比较丰富的,如下(这里json其实就是NSData类型)
array,dictionary<--->model array,model<--->jsonStr<--->json(data)
这四种数据类型在mjextension的作用下可以相互切换,你可以在NSObject+MJKeyValue类中找到方法。用法如下
1.转为array,dictionary
- (id)mj_JSONObject
{
if ([self isKindOfClass:[NSString class]]) {
//jsonStr-->array,dictionary
return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
} else if ([self isKindOfClass:[NSData class]]) {
//json-->array,dictionary
return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
}
return self.mj_keyValues; //这里我们稍后在分析
}
2.转为json(data)
- (NSData *)mj_JSONData
{
//jsonStr-->json(data)
if ([self isKindOfClass:[NSString class]]) {
return [((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding];
} else if ([self isKindOfClass:[NSData class]]) {
return (NSData *)self;
}
//这里通过第一步转成array,dictionary再转成json(data)
return [NSJSONSerialization dataWithJSONObject:[self mj_JSONObject] options:kNilOptions error:nil];
}
3.转为jsonStr
- (NSString *)mj_JSONString
{
if ([self isKindOfClass:[NSString class]]) {
return (NSString *)self;
} else if ([self isKindOfClass:[NSData class]]) {
//json(data)-->jsonStr
return [[NSString alloc] initWithData:(NSData *)self encoding:NSUTF8StringEncoding];
}
//这里先转为json(data)再转成jsonStr
return [[NSString alloc] initWithData:[self mj_JSONData] encoding:NSUTF8StringEncoding];
}
4.转为model array,model
//转model
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
//转model array
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray
查看代码我们很容易发现mj_keyValuesArrayWithObjectArray
方法中循环调用了mj_objectWithKeyValues
方法,最终都会执行到- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
方法。
三、大体思路
现在让我们先来看看MJExtension的目录结构和大致作用
list.png
MJFoundation:判断某个类是否属于Foundation框架
MJPropertyKey:对属性的key进行了简单的包装
MJPropertyType:对属性的类别进行了建档的包装
MJProperty:对属性进行包装,这里会用到MJPropertyKey和MJPropertyType
NSObject+MJProperty:成员属性相关的扩展
NSObject+MJKeyValue:转化的主要逻辑方法,供开发者直接使用
NSObject+MJClass:类相关的扩展方法
NSString+MJExtension:提供了一些对字符串的操作的方法
NSObject+MJCoding:归档解归档方法,供开发直接使用(这个我一般不使用,这里不做分析了就)
大体思路就是NSObject+MJKeyValue
提供api和逻辑,然后调用NSObject+MJProperty
、NSObject+MJProperty
、NSString+MJExtension
的扩展方法对MJProperty
对象操作去实现这四种数据类型相互切换。
四、实现原理
通过上面的代码我们可以了解到,json<--->jsonStr<--->dictionary,array直接的转化都是简单的系统方法。唯一的难点就是model<--->dictionary之间的相互转化。
model--->dictionary
我们回到- (id)mj_JSONObject
方法,这里我们可以看到model--->dictionary最终会调用- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
方法。
1.遍历model 的属性,取出来每个MJProperty对象去检测是否被忽略
2.取出属性对应的value,根据value的类型去做不同的处理。如果是模型类,做递归处理;如果是数组,对数组对处理;如果是nsulr类型就转字符串
3.最后就行赋值,根据propertyKey.type是数组还是字典就行赋值操作。
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = value;
} else {
innerContainer[propertyKey.name.intValue] = value;
}
4.遍历完毕然后字典。
dictionary--->model。
我们聚焦到- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
方法。
1.遍历model 的属性,取出来每个MJProperty对象去检测是否被忽略
2.根据MJProperty.name取出dictionary中对应的value值。
3.然后根据MJProperty对象的类型对取到的value值进行加工。
// 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 *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的过滤
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.复杂处理
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
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) { // 模型属性
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 { // 字典数组-->模型数组
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
// 字符串转码
value = [value mj_url];
} else if (type.isNumberType) {
NSString *oldValue = value;
// NSString -> NSNumber
if (type.typeClass == [NSDecimalNumber class]) {
value = [NSDecimalNumber decimalNumberWithString:oldValue];
} else {
value = [numberFormatter_ numberFromString:oldValue];
}
// 如果是BOOL
if (type.isBoolType) {
// 字符串转BOOL(字符串没有charValue方法)
// 系统会调用字符串的charValue转为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];
五、小技巧
还有个比较头疼的问题,当网络返回的数据变成字典中的key和model的属性命名不一致时。
这里我们让model事项mj提供的方法.这个方法的作用就是在给模型赋值的时候,把右边字段的值赋给模型中左边字段的属性。
+ (NSDictionary *)mj_replacedKeyFromPropertyName{
return @{@"ID":@"id"};
}
网友评论