美文网首页
MJExtension 源码浅析

MJExtension 源码浅析

作者: David_Cap | 来源:发表于2018-06-29 16:31 被阅读68次

    GitHub给个✨

    MJExtension 基本上所有的iOS开发都用过,Json <---> Model。
    主要的机制是采用了Runtime的反射机制,有兴趣学习Runtime的同学可以看看。

    整理思路

    如果你想要写一个 Json转Model的第三方,该怎么入手。

    已知:

    • 包含key value 的 dictionary
    • model类

    那么也就是,我们需要找出model里面的所有的property,然后把dictionary里面的value赋值给对应的property。

    伪代码
    + (id)JsonToModel:(NSDictionary *)dictionary {
        TestModel *model = [[TestModel alloc] init];
        for(propertyName in TestModel){
            [model setValue:dictionary[propertyName] forKey: propertyName];
        }
    }
    
    

    说干就干

    方法入口

    调用:入口方法 mj_objectWithKeyValues最终会调用mj_setKeyValues

    + (instancetype)mj_objectWithKeyValues:(id)keyValues
    {
        return [self mj_objectWithKeyValues:keyValues context:nil];
    }
    
    + (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
    {
        return [[[self alloc] init] mj_setKeyValues:keyValues];
    }
    
    - (instancetype)mj_setKeyValues:(id)keyValues
    {
        return [self mj_setKeyValues:keyValues context:nil];
    }
    
    

    核心方法

    /**
     核心代码:
     */
    - (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
    {
        // 获得JSON对象
        keyValues = [keyValues mj_JSONObject];
        
        Class clazz = [self class];
        
        //通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
        [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
            @try {            
                // 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;
                }
                
                ...
                ...
                安全判断代码
                ...
                ...
                
                // 3.赋值
                [property setValue:value forObject:self];
            } @catch (NSException *exception) {
    
            }
        }];
        
        return self;
    }
    

    上面代码做了一定程度的简化,当然其实可以看的出来和前面的伪代码有点类似了。主要的逻辑就是:

    1. 获取Dictionary
    2. 从类里面拿到所有的propertyName
    3. 找到dictionary里面的value赋值给model

    反射获取类中的属性

    + (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration
    {
        // 获得成员变量
        NSArray *cachedProperties = [self properties];
        
        // 遍历成员变量
        BOOL stop = NO;
        for (MJProperty *property in cachedProperties) {
            enumeration(property, &stop);
            if (stop) break;
        }
    }
    
    #pragma mark - 公共方法
    + (NSMutableArray *)properties
    {
        NSMutableArray *cachedProperties = [NSMutableArray array];
        
        [self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
            // 1.获得所有的成员变量
            unsigned int outCount = 0;
            objc_property_t *properties = class_copyPropertyList(c, &outCount);
            
            // 2.遍历每一个成员变量
            for (unsigned int i = 0; i<outCount; i++) {
                MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
                // 过滤掉Foundation框架类里面的属性
                if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
                property.srcClass = c;
                [property setOriginKey:[self propertyKey:property.name] forClass:self];
                [property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
                [cachedProperties addObject:property];
            }
            
            // 3.释放内存
            free(properties);
        }];
           
        return cachedProperties;
    }
    

    通过*objc_property_t properties = class_copyPropertyList(c, &outCount)这个方法获取所有的property,然后mj_enumerateClasses是遍历SuperClass,最后是把拿到的信息封装成一个MJProperty。

    PS

    其中代码进行了相应的简化,其中有一些Property的缓存,类型的安全判断都暂时忽略,只进行思路的理解。

    纸上谈来终觉浅

    写了一个Dome,大大简化了MJExtension,目的是为了浅尝Runtime的魅力。基本思路如最上面的伪代码。其中很多安全判断,省略了些许。
    这里有一个Property Attribute,苹果文档比较详细参考:文档

    Demo 地址:我是Demo

    //Demo Class
    #import "NSObject+TestExtension.h"
    #import <objc/runtime.h>
    
    static NSSet *foundationClasses_;
    
    @implementation NSObject (TestExtension)
    + (instancetype)test_JsonToModelWithDictionary:(NSDictionary *)dict
    {
        Class clazz = [self class];
        id model = [[clazz alloc] init];
        
        unsigned int count;
        objc_property_t *propertys = class_copyPropertyList(clazz, &count);
        
        //1. 遍历Class中的Property
        for (int i = 0; i < count; i++) {
            NSString *propertyName = @(property_getName(propertys[i]));
            NSString *propertyAttribute =@(property_getAttributes(propertys[i]));
            
            
            // 2. 获取Value
            id value = [dict objectForKey:propertyName];
            if (!value || [value isKindOfClass:[NSNull class]]) {
                continue;
            }
            
            // 3. 如果是其他类,递归调用
            Class propertyClazz = [self getClassFromAttrs:propertyAttribute];
            if (propertyClazz) {
                value = [propertyClazz test_JsonToModelWithDictionary:value];
            }
            
            [model setValue:value forKey:propertyName];
            
        }
        
        return model;
    }
    
    + (Class)getClassFromAttrs:(NSString *)attrs
    {
        NSUInteger dotLoc = [attrs rangeOfString:@","].location;
        NSString *code = nil;
        NSUInteger loc = 1;
        if (dotLoc == NSNotFound) { // 没有,
            code = [attrs substringFromIndex:loc];
        } else {
            code = [attrs substringWithRange:NSMakeRange(loc, dotLoc - loc)];
        }
        
        if(code.length > 3 && [code hasPrefix:@"@\""])
        {
            // 去掉@"和",截取中间的类型名称
            code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
            Class clazz = NSClassFromString(code);
            if (![self isClassFromFoundation:clazz]) {
                return clazz;
            }
        }
        
        return nil;
    }
    
    + (BOOL)isClassFromFoundation:(Class)c
    {
        if (foundationClasses_ == nil) {
            foundationClasses_ = [NSSet setWithObjects:
                                  [NSURL class],
                                  [NSDate class],
                                  [NSValue class],
                                  [NSData class],
                                  [NSError class],
                                  [NSArray class],
                                  [NSDictionary class],
                                  [NSString class],
                                  [NSAttributedString class], nil];
        }
        
        if (c == [NSObject class]) return YES;
        
        __block BOOL result = NO;
        [foundationClasses_ enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) {
            if ([c isSubclassOfClass:foundationClass]) {
                result = YES;
                *stop = YES;
            }
        }];
        return result;
    }
    
    
    @end
    
    

    相关文章

      网友评论

          本文标题:MJExtension 源码浅析

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