MJExtension

作者: 闹鬼的金矿 | 来源:发表于2017-07-21 16:23 被阅读226次

    总结

    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;
     
    }
    

    相关文章

      网友评论

        本文标题:MJExtension

        本文链接:https://www.haomeiwen.com/subject/hlyekxtx.html