MJExtension

总结

1.KVC,字典转化成对象的时候,需要给对象的属性赋值。MJExtentsion是通过KVC实现的,所以对象都需要继承NSObject。

2.Runtime

1)对与某一个类型,通过runtime去查找它自己所有的属性,再根据属性去字典里查找对应的value。

2)通过runtime在运行时给对象增加字段信息,比如记录哪些属性进行转化,哪些属性忽略转化。

3)通过runtime给对应的类增加缓存信息,提高转化效率。

3.递归,针对对象中又包含对象,数组包含对象等情况,通过递归实现属性的赋值。

4.self用在类方法中意思是代表当前类,用在对象方法中代表当前对象。通过一个实例对象的指针调用一个类方法可以这么做:

Class cls = [self class];
[cls classMethod];

5.instanceType

+ (instancetype)objectWithKeyValues:(id)keyValues

该方法定义在NSObject中,但在不同的子业务类型中,通过instanceType会返回具体的类型对象。

需要注意的地方

1.字典转对象的时候,如果某个属性是NULL,会被过滤,也就是该对象的这个属性的值是默认值。整型是0,对象类型是nil。

2.在对象转字典的时候,如果对象本身的属性包含了superClass,debugDescription,description,hash这四种中的一种,会被MJExtension过滤掉。因为它认为是系统自动增加的元素,所以当前情况下,需要把这些属性手动增加上去。

3.在对象转字典的时候,如果对象某个字段为nil,生成的结果字典里就不会存在这个键值对。

4.在对象中的属性与字典的字段不匹配的时候,需要手动指定。指定的方式是自己实现一个replacedKeyFromPropertyName方法,这个方法是手写的,写错一个字母转化就会有问题。不过也有别的方式,可以通过setupObjectWithBlock去指定,但是key必须传MJReplacedKeyFromPropertyNameKey,因为它取的时候是按这个key去取的。

核心实现

MJExtension中最核心的两个函数分别是

- (instancetype)setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context error:(NSError **)error;

- (NSDictionary *)keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys error:(NSError __autoreleasing)error

其他的函数都是最终调用这两个函数,只是封装了一下参数

1.字典转对象:

- (instancetype)setKeyValues:(id)keyValues context:(NSManagedObjectContext *)
context error:(NSError *__autoreleasing *)error
 
{
 
    // 如果是JSON字符串
 
    if ([keyValues isKindOfClass:[NSString class]]) {
  
    //json格式字符串转字典,通过NSJONSerializaton 先字符串转nsdata 再反序列化成nsdictionary
        keyValues = [((NSString *)keyValues) JSONObject];
    }
     
    MJAssertError([keyValues isKindOfClass:[NSDictionary class]], self, error, 
        @"keyValues参数不是一个字典");
 
    @try {
 
        Class aClass = [self class];
//哪些属性需要被转化
        NSArray *allowedPropertyNames = [aClass totalAllowedPropertyNames];
//哪些属性不需要转化
        NSArray *ignoredPropertyNames = [aClass totalIgnoredPropertyNames];
 
         
 
        //通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
 
        [aClass enumeratePropertiesWithBlock:^(MJProperty *property, BOOL *stop
            ) {
 
            // 0.检测是否被忽略
 
            if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
 
            if ([ignoredPropertyNames containsObject:property.name]) return;
 
             
 
            // 1.取出属性值
 
            id value = keyValues ;
//得到当前属性对应到字典中的字段名称或者路径
            NSArray *keys = [property keysFromClass:[self class]];
//这个循环覆盖到了某个属性对应的是字典中某个path的情况,
//例如对象的oldName属性对应到字典中是name.oldName的情况
            for (NSString *key in keys) {
 
                if (![value isKindOfClass:[NSDictionary class]]) continue;
                value = value[key];
 
            }
 
            if (!value || value == [NSNull null]) return;
 
             
 
            // 2.如果是模型属性
 
            MJType *type = property.type;
 
            Class typeClass = type.typeClass;
//当前属性是一个数组,得到数组中的对象类型
            Class objectClass = [property objectClassInArrayFromClass:[self 
            class]];
 
            if (!type.isFromFoundation && typeClass) {
//当前属性是一个业务对象,递归
                value = [typeClass objectWithKeyValues:value context:context 
                error:error];
 
            } else if (objectClass) {
 
                // 3.字典数组-->模型数组
 
                value = [objectClass objectArrayWithKeyValuesArray:value 
                context:context error:error];
//以下主要是处理属性和字典中的数据类型不匹配的问题,进行数据类型的转换
            } else if (typeClass == [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 (typeClass == [NSURL class]) {
 
                    // NSString -> NSURL
 
                    value = [NSURL URLWithString:value];
 
                } else if (type.isNumberType) {
 
                    NSString *oldValue = value;
 
                     
 
                    // NSString -> NSNumber
 
                    value = [_numberFormatter numberFromString:oldValue];
 
                     
 
                    // 如果是BOOL
 
                    if ([type.code isEqualToString:MJTypeBOOL]) {
 
                        // 字符串转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;
 
                        }
 
                    }
 
                }
 
            }
 
             
 
            // 4.赋值
 
            [property setValue:value forObject:self];
 
        }];
 
         
 
        // 转换完毕
 
        if ([self respondsToSelector:@selector(keyValuesDidFinishConvertingToObject)]) {
 
            [self keyValuesDidFinishConvertingToObject];
 
        }
 
    } @catch (NSException *exception) {
 
        MJBuildError(error, exception.reason);
 
    }
 
    return self;
 
}

2.对象转字典

- (NSDictionary *)keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)
ignoredKeys error:(NSError *__autoreleasing *)error
 
{  
    // 如果自己不是模型类
    if ([MJFoundation isClassFromFoundation:[self class]]) return (NSDictionary *)self;
 
    __block NSMutableDictionary *keyValues = [NSMutableDictionary dictionary];
 
    @try {
 
        Class aClass = [self class];
 
        NSArray *allowedPropertyNames = [aClass totalAllowedPropertyNames];
 
        NSArray *ignoredPropertyNames = [aClass totalIgnoredPropertyNames];
 
        [aClass enumeratePropertiesWithBlock:^(MJProperty *property, BOOL *stop
            ) {
 
            // 0.检测是否被忽略
 
            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;
 
            // 1.取出属性值
 
            id value = [property valueFromObject:self];
 
            if (!value) return;
 
            // 2.如果是模型属性
 
            MJType *type = property.type;
 
            Class typeClass = type.typeClass;
 
            Class objectClass = [property objectClassInArrayFromClass:[self class]];
 
            if (!type.isFromFoundation && typeClass) {
 
                value = [value keyValues];
 
            } else if (objectClass) {
 
                // 3.处理数组里面有模型的情况
 
                value = [objectClass keyValuesArrayWithObjectArray:value];
 
            } else if (typeClass == [NSURL class]) {
 
                value = [value absoluteString];
 
            }
 
            // 4.赋值
 
            NSArray *keys = [property keysFromClass:[self class]];
 
            NSUInteger keyCount = keys.count;
 
            // 创建字典
 
            __block NSMutableDictionary *innerDict = keyValues;
//覆盖对象属性对应字典的一个path的情况
            [keys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, 
                BOOL *stop) {
 
                if (idx == keyCount - 1) { // 最后一个属性
 
                    innerDict[key] = value;
 
                } else { // 字典
 
                    NSMutableDictionary *tempDict = innerDict[key];
 
                    if (tempDict == nil) {
 
                        tempDict = [NSMutableDictionary dictionary];
 
                        innerDict[key] = tempDict;
 
                    }
 
                    innerDict = tempDict;
 
                }
 
            }];
 
        }];
 
      
        // 去除系统自动增加的元素
 
        [keyValues removeObjectsForKeys:@[@"superclass", @"debugDescription", 
        @"description", @"hash"]];
 
         
        // 转换完毕
 
        if ([self respondsToSelector:@selector(objectDidFinishConvertingToKeyValues)]) {
 
            [self objectDidFinishConvertingToKeyValues];
 
        }
 
    } @catch (NSException *exception) {
 
        MJBuildError(error, exception.reason);
 
    }
 
    return keyValues;
}

现在来看一下,MJExtension是如何通过属性名称去查找对应在字典中的字段,逻辑主要在NSObject+MJPro
perty.m的

+ (NSString *)propertyKey:(NSString *)propertyName方法

+ (NSString *)propertyKey:(NSString *)propertyName
 
{
 
    MJAssertParamNotNil2(propertyName, nil);
 
     
 
    __block NSString *key = nil;
 
    // 1.查看有没有需要替换的key,这里检查当前类有没有实现replacedKeyFromPropertyName方法,先从这里取
 
    if ([self respondsToSelector:@selector(replacedKeyFromPropertyName)]) {
 
        key = [self replacedKeyFromPropertyName][propertyName];
 
    }
 
     
 
    if (!key) {
//2.如果没有从replacedKeyFromPropertyName中得到,再检查有没有通过setupObjectWithBlock指定
        [self enumerateClassesWithBlock:^(__unsafe_unretained Class c, BOOL *
            stop) {
 
            NSDictionary *dict = objc_getAssociatedObject(c, &
                MJReplacedKeyFromPropertyNameKey);
 
            if (dict) {
 
                key = dict[propertyName];
 
            }
 
            if (key) *stop = YES;
 
        }];
 
    }
 
     
 
    // 3.以上都没有的情况下,就用属性名作为key,换句话说只有属性名称跟字典中的key名称对应不上的
    //时候才需要用1或者2的方式指定,默认情况就是取属性名称。
 
    if (!key) key = propertyName;
 
     
    return key;
 
}

缓存

MJEextension中通过runtime的objc_setAssociatedObject函数,实现属性信息的缓存,对于某一个类型只要之前获取了它的属性信息之后再次获取即可从缓存中获取,不需要再次生成属性信息。

1.在获取一个类的所有属性时候:

+ (NSArray *)properties
{
    static const char MJCachedPropertiesKey = '\0';
 
    // 获得成员变量
 
    // 通过关联对象,以及提前定义好的MJCachedPropertiesKey来进行运行时,对所有属性的获取。
 
    //***objc_getAssociatedObject 
    //方法用于判断当前是否已经获取过MJCachedPropertiesKey对应的关联对象
 
    //  1> 关联到的类对象
 
    //  2> 关联的属性 key
 
 
//先从类的缓存对象中获取
    NSMutableArray *cachedProperties = objc_getAssociatedObject(self, &
        MJCachedPropertiesKey);
 
    //***
 
    if (cachedProperties == nil) {
 
        cachedProperties = [NSMutableArray array];
 
        /**遍历这个类的父类*/
 
        [self enumerateClassesWithBlock:^(__unsafe_unretained Class c, BOOL *
            stop) {
 
            // 1.获得所有的成员变量
 
            unsigned int outCount = 0;
 
            /**
 
                class_copyIvarList 成员变量,提示有很多第三方框架会使用 Ivar,能够获得更多的信息
 
                但是:在 swift 中,由于语法结构的变化,使用 Ivar 非常不稳定,经常会崩溃!
 
                class_copyPropertyList 属性
 
                class_copyMethodList 方法
 
                class_copyProtocolList 协议
 
                */
 
            objc_property_t *properties = class_copyPropertyList(c, &outCount);
             
 
            // 2.遍历每一个成员变量
 
            for (unsigned int i = 0; i<outCount; i++) {
 
                MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
 
                property.srcClass = c;
 
                [property setKey:[self propertyKey:property.name] forClass:self];
 
                [property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
 
                [cachedProperties addObject:property];
 
            }
 
            // 3.释放内存
 
            free(properties);
 
        }];
 
        //*** 在此时设置当前这个类为关联对象,这样下次就不会重复获取类的相关属性。
 
        objc_setAssociatedObject(self, &MJCachedPropertiesKey, cachedProperties
            , OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 
        //***
    }
 
    return cachedProperties;
}

2.在获取一个runtime属性对应的MJProperty对象的时候:

+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
 
    MJProperty *propertyObj = objc_getAssociatedObject(self, property);
 
    if (propertyObj == nil) {
 
        propertyObj = [[self alloc] init];
 
        propertyObj.property = property;
 
        objc_setAssociatedObject(self, property, propertyObj, 
            OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 
    }
 
    return propertyObj;
 
}

3.在获取一个MJProperty的type的时候:

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

推荐阅读更多精彩内容