MJExtension原理分析

本文详细介绍了MJExtension的原理,包括字典转模型、模型转字典、字典数组转模型数组的过程,以及宏、加锁、归档等机制。通过对MJExtension的深入分析,帮助开发者理解其工作方式,提升iOS开发效率。

 MJExtension简述

MJExtension是一个非常容易使用并且功能强大的第三方框架,用于模型(model)与JSON之间进行相互转化!帮助开发者可高效方便地进行数据格式的转换,大大节省了开发者的开发时间。而且开发者也可以进行自定义转换模型字段或者忽略某些需转换的字段等,一旦你的项目用上将会爱不释手!

 

预热

#define MJExtensionAssertError(condition, returnValue, clazz, msg) \
[clazz setMj_error:nil]; \
if ((condition) == NO) { \
    MJExtensionBuildError(clazz, msg); \
    return returnValue;\
}
#define MJExtensionBuildError(clazz, msg) \
NSError *error = [NSError errorWithDomain:msg code:250 userInfo:nil]; \
[clazz setMj_error:error];

分析:通过判断condition是否成立来设置类的NSError,通过创建NSObject分类使用runtime来绑定类对应的当前错误mj_error。

  • 加锁
// 信号量
#define MJExtensionSemaphoreCreate \
static dispatch_semaphore_t signalSemaphore; \
static dispatch_once_t onceTokenSemaphore; \
dispatch_once(&onceTokenSemaphore, ^{ \
    signalSemaphore = dispatch_semaphore_create(1); \
});

#define MJExtensionSemaphoreWait \
dispatch_semaphore_wait(signalSemaphore, DISPATCH_TIME_FOREVER);

#define MJExtensionSemaphoreSignal \
dispatch_semaphore_signal(signalSemaphore);

分析:使用dispatch_semaphore_create创建一个信号量使得程序执行的线程安全,当有线程在执行时则加锁,保证其它线程处于等待中;

原理分析

  • 字典转模型
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 获得JSON对象
    keyValues = [keyValues mj_JSONObject];
    
    // keyValues不是字典则返回不处理
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
    
    Class clazz = [self class];
    // 检测是否有重写mj_allowedPropertyNames方法自定义允许转换的字段
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    // 检测是否有重写mj_ignoredPropertyNames方法自定义忽略不进行转换的字段
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
    
    //通过封装的方法回调一个通过运行时编写的方法,property为转换模型中对应各属性生成的对象。
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
       // 当前模型property.name不在允许转换字段的数组allowedPropertyNames里面,不做处理
            if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
       // 当前模型property.name在忽略转换字段的数组ignoredPropertyNames里面,也不做处理
            if ([ignoredPropertyNames containsObject:property.name]) return;

            // 取出相应的属性值
            id value;
            // 获取模型属性对应的key数组
            NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
            for (NSArray *propertyKeys in propertyKeyses) {
                value = keyValues;
                for (MJPropertyKey *propertyKey in propertyKeys) {
                    // 获取propertyKey中name对应的值value
                    value = [propertyKey valueInObject:value];
                }
                if (value) break;
            }
            
            // 是否有实现mj_newValueFromOldValue设置获取新值
            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]];
            // value为字典,propertyClass为模型类,将字典转为模型
            if (!type.isFromFoundation && propertyClass) { 
                value = [propertyClass mj_objectWithKeyValues:value context:context];
            } else if (objectClass) {
                if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
                    // objectClass为数组里的url,直接转换
                    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 {
                
                // ...忽略部分代码...
                // ...是isFromFoundation类型,进行Foundation类型进行相应处理...
                // 例如:NSString,NSURL
            }
            
            // 对模型model属性进行赋值
            [property setValue:value forObject:self];
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];
    
    // ...忽略部分代码...

    return self;
}
  • 字典转模型过程:
  1. 将传进来keyValues进行处理为字典,不是字典则返回不进行下面的转换操作;
  2. 遍历类将类对象属性转换设置为MJProperty对象;
  3. 通过MJPropertyKey中存放的key去字典中获取对应的值value;
  4. 若value值为字典,则再进行模型转换;若value为字典数组,则将里面字典转为模型存放进数组里;若value是类似NSString或NSNumber类型,则进行相应的处理;
  5. 将处理完的value通过key设置模型对象属性对应的value;
  • 字典数组转模型数组
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context
{
    // 如果是JSON字符串,转换为数组或字典等
    keyValuesArray = [keyValuesArray mj_JSONObject];
    
    // 判断keyValuesArray参数是否为数组
    MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray参数不是一个数组");
    
    // 如果数组里面放的是NSString、NSNumber等数据,则不用转换,直接放入数组
    if ([MJFoundation isClassFromFoundation:self]) return [NSMutableArray arrayWithArray:keyValuesArray];

    NSMutableArray *modelArray = [NSMutableArray array];
    
    for (NSDictionary *keyValues in keyValuesArray) {
        // keyValues是数组,递归mj_objectArrayWithKeyValuesArray方法
        if ([keyValues isKindOfClass:[NSArray class]]){
            [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];
        } else {
        // keyValues是字典,转换为模型存入数组
            id model = [self mj_objectWithKeyValues:keyValues context:context];
            if (model) [modelArray addObject:model];
        }
    }
    
    return modelArray;
}
  • 字典数组转模型数组:
  1. 如果是json字符串,则调用mj_JSONObject转换为数组;
  2. 遍历当前数组,若里面存放的仍然是数组,则递归继续调用mj_objectArrayWithKeyValuesArray方法,直到遍历到的为字典,才将字典转换为模型,再将模型存进数组;若存放的是字典,直接转换为模型存放进数组;
  • 模型转字典
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
{
    // 如果自己不是模型类, 那就返回自己
    MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定义的模型类")
    
    id keyValues = [NSMutableDictionary dictionary];
    
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
            // 忽略部分代码
            // 1.取出属性值
            id value = [property valueForObject:self];
            if (!value) return;
            
            // 2.如果是模型属性
            MJPropertyType *type = property.type;
            Class propertyClass = type.typeClass;
            if (!type.isFromFoundation && propertyClass) {
                value = [value mj_keyValues];
            } else if ([value isKindOfClass:[NSArray class]]) {
                // 3.处理数组里面有模型的情况
                value = [NSObject mj_keyValuesArrayWithObjectArray:value];
            } else if (propertyClass == [NSURL class]) {
                value = [value absoluteString];
            }

            // 4.赋值
            if ([clazz mj_isReferenceReplacedKeyWhenCreatingKeyValues]) {
                NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
                NSUInteger keyCount = propertyKeys.count;
                // 创建字典
                __block id innerContainer = keyValues;
                [propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
                    // 下一个属性
                    MJPropertyKey *nextPropertyKey = nil;
                    if (idx != keyCount - 1) {
                        nextPropertyKey = propertyKeys[idx + 1];
                    }
                    
                    if (nextPropertyKey) { // 不是最后一个key
                        // 若不是最后一个,则在前一个判断是字典或数组新生成一个数组或字典
                        // 当前propertyKey对应的字典或者数组
                        id tempInnerContainer = [propertyKey valueInObject:innerContainer];
                        innerContainer = tempInnerContainer;
                    } else { // 对最后一个key进行value赋值
                        if (propertyKey.type == MJPropertyKeyTypeDictionary) {
                            innerContainer[propertyKey.name] = value;
                        } else {
                            innerContainer[propertyKey.name.intValue] = value;
                        }
                    }
                }];
            } else {
                // 无引用自父类,直接赋值
                keyValues[property.name] = value;
            }
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];
    
    
    return keyValues;
}
  • 模型转字典:
  1. 通过当前模型类生成并获取对应属性的MJProperty对象;
  2. 通过MJProperty中的key在当前模型中获取对应的value值,将value及对应的key存放在字典中;

 

其它方法

  • 获取JSON对象
// 获得转换后的JSON对象
keyValues = [keyValues mj_JSONObject];
    
- (id)mj_JSONObject
{
    if ([self isKindOfClass:[NSString class]]) {
        return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
    } else if ([self isKindOfClass:[NSData class]]) {
        return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
    }
    
    return self.mj_keyValues;
}
  1. 若当前对象为NSString类,则将当前对象转为NSData再调用JSONObjectWithData方法将字符串转为字典;
  2. 若当前对象为NSData类,直接调用JSONObjectWithData方法将字符串转为字典;
  3. 若对象是用户自定义的模型,则调用mj_keyValues方法,内部是通过调用[self mj_keyValuesWithKeys:nil ignoredKeys:nil]进行实现,将此时的模型转为字典;
  • 判断是否为NSFoundation类
+ (BOOL)isClassFromFoundation:(Class)c
{
    if (c == [NSObject class] || c == [NSManagedObject class]) return YES;
    
    // 集合中没有NSObject,因为几乎所有的类都是继承自NSObject,具体是不是NSObject需要特殊判断
    foundationClasses = [NSSet setWithObjects:
                              [NSURL class],
                              [NSDate class],
                              [NSValue class],
                              [NSData class],
                              [NSError class],
                              [NSArray class],
                              [NSDictionary class],
                              [NSString class],
                              [NSAttributedString class], nil];
    });
    
    __block BOOL result = NO;
    [foundationClasses enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) {
        if ([c isSubclassOfClass:foundationClass]) {
            result = YES;
            *stop = YES;
        }
    }];
    return result;
}
  1. 若当前类为NSObject或NSManagedObject,则直接返回YES;
  2. 使用单例实现foundationClasses集合;
  3. 遍历集合判断传进来的类c是否等于集合中所包含类或子类;
  • 遍历当前类
// 用于遍历子类及父类以至于获取类属性
+ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration
{
    // 1.没有block就直接返回
    if (enumeration == nil) return;
    
    // 2.停止遍历的标记
    BOOL stop = NO;
    
    // 3.当前正在遍历的类
    Class c = self;
    
    // 4.开始遍历每一个类
    while (c && !stop) {
        // 4.1.执行操作
        enumeration(c, &stop);
        
        // 4.2.获得父类
        c = class_getSuperclass(c);
        
        // 若父类不是Fundatiaon类,退出遍历循环
        if ([MJFoundation isClassFromFoundation:c]) break;
    }
}
  • 遍历模型类属性生成MJProperty对象
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];
                    
         NSLog(@"打印属性:%@", property.name);
}
                
     // 3.释放内存
     free(properties);
  1. 遍历每个属性生成对应的MJProperty对象;
  2. 给当前property对象设置name,name也就是当前属性key;
  3. 给当前property对象设置ObjectClass,ObjectClass就是当前属性对应的类型;
  • 设置当前属性类型code
- (void)setCode:(NSString *)code
{
    // 忽略代码
}
  1. 通过MJPropertyType包装当前类属性所对应的相关属性;
  2. 用户可通过获取MJProperty的type属性就是MJPropertyType;
  • 设置key的多级映射
//假设传进来的stringKey = @"name.info[1].nameChangedTime"
- (NSArray *)propertyKeysWithStringKey:(NSString *)stringKey
{
    if (stringKey.length == 0) return nil;
    
    NSMutableArray *propertyKeys = [NSMutableArray array];
    // 如果有多级映射
    NSArray *oldKeys = [stringKey componentsSeparatedByString:@"."];
    
    for (NSString *oldKey in oldKeys) {
        NSUInteger start = [oldKey rangeOfString:@"["].location;
        if (start != NSNotFound) { // 有索引的key
            NSString *prefixKey = [oldKey substringToIndex:start];
            NSString *indexKey = prefixKey;
            if (prefixKey.length) {
                MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init];
                propertyKey.name = prefixKey;
                [propertyKeys addObject:propertyKey];
                
                indexKey = [oldKey stringByReplacingOccurrencesOfString:prefixKey withString:@""];
            }

            // 元素
            NSArray *cmps = [[indexKey stringByReplacingOccurrencesOfString:@"[" withString:@""] componentsSeparatedByString:@"]"];
            for (NSInteger i = 0; i<cmps.count - 1; i++) {
                MJPropertyKey *subPropertyKey = [[MJPropertyKey alloc] init];
                subPropertyKey.type = MJPropertyKeyTypeArray;
                subPropertyKey.name = cmps[i];
                [propertyKeys addObject:subPropertyKey];
            }
        } else { // 没有索引的key
            MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init];
            propertyKey.name = oldKey;
            [propertyKeys addObject:propertyKey];
        }
    }
        
    return propertyKeys;
}
  1. 若字符串stringKey存在多级映射,则通过截获模型属性或数组下标,将字符串拆解的不同的属性key设置在MJPropertyKey对象的name,再将其存放进数组。假设stringKey为"name.info[1].nameChangedTime",首先,通过" . "将字符串拆分成数组,再通过" [ "将下标1拿出来,通过生成四个对应的MJPropertyKey存放进数组中;
  2. 若字符串stringKey不存在多级映射,则直接生成MJPropertyKey对象存放进数组;
  • 通过属性key设置对应类型
+ (Class)propertyObjectClassInArray:(NSString *)propertyName
{
    __block id clazz = nil;
    if ([self respondsToSelector:@selector(mj_objectClassInArray)]) {
        clazz = [self mj_objectClassInArray][propertyName];
    }
    // 兼容旧版本
    if ([self respondsToSelector:@selector(objectClassInArray)]) {
        clazz = [self performSelector:@selector(objectClassInArray)][propertyName];
    }
    
    if (!clazz) {
        [self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
            NSDictionary *dict = objc_getAssociatedObject(c, &MJObjectClassInArrayKey);
            if (dict) {
                clazz = dict[propertyName];
            }
            if (clazz) *stop = YES;
        }];
    }
    
    // 如果是NSString类型
    if ([clazz isKindOfClass:[NSString class]]) {
        clazz = NSClassFromString(clazz);
    }
    return clazz;
}
  1. 通过判断当前mj_objectClassInArray方法是否实现,若实现了,是否有当前key对应的类型,存在的话,则获取到的就为当前属性key所对应的类型;
  2. 若mj_objectClassInArray方法未实现,若是字符串则直接转为NSString类,否则为nil;
  • 归档
- (void)mj_encode:(NSCoder *)encoder
{
    // 忽略部分代码
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        // 检测是否被忽略
        id value = [property valueForObject:self];
        if (value == nil) return;
        [encoder encodeObject:value forKey:property.name];
    }];
}

- (void)mj_decode:(NSCoder *)decoder
{
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        // 检测是否被忽略
        id value = [decoder decodeObjectForKey:property.name];
        if (value == nil) { // 兼容以前的MJExtension版本
            value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
        }
        if (value == nil) return;
        [property setValue:value forObject:self];
    }];
}

分析:通过设置宏可以简单地实现归档,直接将宏放入需实现的类中!

 

以上是对MJExtension简单分析,因为涉及的技术比较复杂,所以通过代码多加解释,希望能够讲解清楚,文中若有错误地地方,望指正!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值