MJExtension

一、初始

其实苹果爸爸也给我们提供了字典转模型的方法(如下图),就是容错性不高。

- (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:方法。

error.png

二、基本用法

相比原生,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+MJPropertyNSObject+MJPropertyNSString+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"};
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容