MJExtension源码解读

MJExtension

A fast, convenient and nonintrusive conversion framework between JSON and model.
转换速度快、使用简单方便的字典转模型框架

我们经常需要从网络上拉取json数据,然后将json数据转化为自己的模型数据,将json数据转化为我们自己的模型数据经常使用的框架有YYModel和MJExtension,所以现在也是打算花一些时间看一下MJExtension的源码,并且写一篇博客记录一下,因为不记录下来的话感觉很容易忘,学习效果不佳。

使用MJExtension

1.pod 'MJExtension'
2.#import "MJExtension.h"
3.开始使用

最简单的使用

模型:

//User.h
@interface User : NSObject

@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *icon;
@property (nonatomic, assign)unsigned int age;
@property (nonatomic, copy)NSString *height;
@property (nonatomic, strong)NSNumber *money;

@end

字典转模型:

//ViewController.m
NSDictionary *dict = @{
                           @"name" : @"Jack",
                           @"icon" : @"lufy.png",
                           @"age" : @20,
                           @"height" : @"1.55",
                           @"money" : @100.9
                           };
    
    // JSON -> User
    User *user = [User mj_objectWithKeyValues:dict];
    
    NSLog(@"name=%@, icon=%@, age=%u, height=%@, money=%@", user.name, user.icon, user.age, user.height, user.money);

打印结果:

 name=Jack, icon=lufy.png, age=20, height=1.55, money=100.9

通过一句简单的代码,就把字典数据转化为了模型数据,非常方便简洁。

复杂一点的应用

很多时候json转模型都不是这样简单。有时候会出现模型中嵌套模型或者模型中的属性名和json数据中的key不一致的情况。
下面看一下一个Student类的模型:

//Student.h
@interface Student : NSObject

@property (nonatomic, copy)NSString *ID;
@property (nonatomic, copy)NSString *desc;
@property (nonatomic, copy)NSString *nowName;
@property (nonatomic, copy)NSString *oldName;
@property (nonatomic, copy)NSString *nameChangedTime;
@property (nonatomic, strong)Bag *bag;

@end

我们看到Student模型中嵌套着Bag这个模型:

//Bag.h
@interface Bag : NSObject

@property (nonatomic, copy)NSString *name;
@property ( nonatomic, assign)double *price;

@end

然后我们再看一下json数据:

NSDictionary *dict = @{
                           @"id" : @"20",
                           @"description" : @"kids",
                           @"name" : @{
                                   @"newName" : @"lufy",
                                   @"oldName" : @"kitty",
                                   @"info" : @[
                                           @"test-data",
                                           @{
                                               @"nameChangedTime" : @"2013-08"
                                               }
                                           ]
                                   },
                           @"other" : @{
                                   @"bag" : @{
                                           @"name" : @"a red bag",
                                           @"price" : @100.7
                                           }
                                   }
                           };

可以看到字典数据中是id,而模型中是ID,同样也有desc和description。模型中有newName和oldName这些属性,而字典中这些属性在name字段下面。bag属性也是一样的道理,那么怎么办呢?
我们只需要实现MJExtension中的+ (NSDictionary *)mj_replacedKeyFromPropertyName方法,在Student.m中#import <MJExtension.h>然后实现+ (NSDictionary *)mj_replacedKeyFromPropertyName方法:

//Student.m
+ (NSDictionary *)mj_replacedKeyFromPropertyName
{
    return @{
             @"ID" : @"id",
             @"desc" : @"description",
             @"oldName" : @"name.oldName",
             @"nowName" : @"name.newName",
             @"nameChangedTime" : @"name.info[1].nameChangedTime",
             @"bag" : @"other.bag"
             };
}

这个方法的作用就是在给模型赋值的时候,把右边字段的值赋给模型中左边字段的属性。
转化一下试试:

// JSON -> Student
    Student *stu = [Student mj_objectWithKeyValues:dict];
    
    // Printing
    NSLog(@"ID=%@, desc=%@, oldName=%@, nowName=%@, nameChangedTime=%@",
          stu.ID, stu.desc, stu.oldName, stu.nowName, stu.nameChangedTime);
    // ID=20, desc=kids, oldName=kitty, nowName=lufy, nameChangedTime=2013-08

    NSLog(@"bagName=%@, bagPrice=%d", stu.bag.name, stu.bag.price);
    // bagName=a red bag, bagPrice=100.700000

这个地方需要关注一个地方就是模型中的nameChangedTime这个属性,在字典中去取值的时候是取name.info[1].nameChangedTime这个字段的值,这个在后面我们讲核心源码的时候会用到。后面讲源码也会以上面这个为例子来讲,这样比较好理解。

MJExtension核心类简介

MJFoundation
  • 这个类中只有一个方法,就是+ (BOOL)isClassFromFoundation:(Class)c,这个方法用来判断一个类是否是foundation类及其子类。
MJProperty

这个类非常重要,这个类是对我们类中属性的再封装。
首先会通过runtime的方法去遍历类中的属性:

    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([Student class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = propertyList[i];
        const char *propertyName = property_getName(property);
        const char *attris = property_getAttributes(property);
        NSLog(@"%s %s", propertyName, attris);
    }
    
    free(propertyList);

打印结果:

ID T@"NSString",C,N,V_ID
desc T@"NSString",C,N,V_desc
nowName T@"NSString",C,N,V_nowName
oldName T@"NSString",C,N,V_oldName
nameChangedTime T@"NSString",C,N,V_nameChangedTime
bag T@"Bag",&,N,V_bag

通过char类型的attris字符串我们可以看到,它中间有一个串是表示它是属于哪一个类的,比如NSString,Bag。

通过遍历类的属性,我们得到了objc_property_t类型的属性对象,然后使用这个objc_property_t对象来创建一个对应的MJProperty对象,我们看看MJ大神是怎么做的:

#pragma mark - 缓存
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
    MJExtensionSemaphoreCreate
    MJExtensionSemaphoreWait
    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);
    }
    MJExtensionSemaphoreSignal
    return propertyObj;
}

首先MJ大神通过objc_property_t对象这个key去缓存中取,如果缓存中取不到,那么就根据objc_property_t来创建一个MJProperty对象,并且把这个MJProperty对象通过property这个key与MJProperty类对象关联起来。那么下次如果再从缓存中取同一个objc_property_t对应的MJProperty对象就能取到了,就不用再创建了。这也是MJ大神使用缓存的一个地方。
上面代码块中propertyObj.property = property;这行代码触发了MJProperty对象的set方法:

9B294CD3-D9D5-4C36-ABAA-5EF331EFCA73.png

MJProperty有一个type属性,这个属性是MJPropertyType类的,就是表示MJProperty对象的property属性是属于什么类型的。

另外每一个MJProperty对象还持有着两个字典,一个是propertyKeysDict,一个是objectClassInArrayDict

  • propertyKeysDict
    这个字典的key是NSStringFromClass(class),值是一个数组,比如在复杂一点的应用中,给模型中的nameChangedTime这个属性赋值的时候,在字典中去取值的时候要对应name.info[1].nameChangedTime这个字段的值。那么就要把name,info,1,nameChangedTim,这个四个字段分别封装为一个MJPropertyKey,加入一个数组中,作为value。这个数组在最终取值的时候会用到。
  • objectClassInArrayDict
    这个字典的key也是NSStringFromClass(class),值是一个类对象,表示如果这个MJProperty对象的类型是数组,并且数组中的元素类型是模型,那么这个个字典的value就是模型的类对象。
MJPropertyKey

上面说过,给模型中的nameChangedTime这个属性赋值的时候,在字典中取值的时候要对应name.info[1].nameChangedTime这个字段的值,那么就要把name,info,1,nameCHangedTime这四个字段分别封装成一个MJPropertyKey。

它有两个属性,一个属性是name,也就是name,info,1这种,还有一个就是type它是自定义的MJPropertyKeyType类型的枚举值,这个枚举值有两种类型,即MJPropertyKeyTypeDictionaryMJPropertyKeyTypeArray,像name,info这种就属于MJPropertyKeyTypeDictionary类型的,1就属于MJPropertyKeyTypeArray类型的。这个也是在取值的时候用的,类型是MJPropertyKeyTypeDictionary就是从字典中取值,类型是MJPropertyKeyTypeArray就是从数组中取值。

MJPropertyType

MJProperty类有一个属性是type,这个属性是MJPropertyType类的,这个type属性就是表征这个MJProperty对象它的property属性属于什么类,NSString类或者NSNumber类等等。
MJProperty对象的type是通过截取property的attributes得到code然后初始化为MJPropertyType对象得到的:

_type = [MJPropertyType cachedTypeWithCode:code];
休息一下

核心源码分析

我们就从复杂一点的应用这个例子去看一下MJExtension的核心源码。
沿着+ (instancetype)mj_objectWithKeyValues:(id)keyValues这个方法一直往下查找就能找到其核心代码:

- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 获得JSON对象
    keyValues = [keyValues mj_JSONObject];
    
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
    
    Class clazz = [self class];
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
    
    //通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
            // 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];
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];
    
    // 转换完毕
    if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
        [self mj_keyValuesDidFinishConvertingToObject];
    }
    return self;
}

这一部分代码很长,我们一部分一部分来看:

1.将json数据转化为foundation类型:
    // 获得JSON对象
    keyValues = [keyValues mj_JSONObject];
    
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
    
    Class clazz = [self class];
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];

allowedPropertyNames是允许进行字典和模型转换的属性名数组,ignoredPropertyNames是不允许进行字典和模型转换额属性名数组,这两个数组一般都是自己在模型类的.m文件中去设置的。

2.遍历整个类的属性:
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration
{
    // 获得成员变量
    NSArray *cachedProperties = [self properties];
    
    // 遍历成员变量
    BOOL stop = NO;
    for (MJProperty *property in cachedProperties) {
        enumeration(property, &stop);
        if (stop) break;
    }
}

再看一下+ (NSMutableArray *)properties方法,其核心部分如下:

 [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);
    }];

首先通过+ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration这个方法去遍历当前模型类及其父类,当追溯到Foundation类型的类时就停止遍历。

有一点需要注意的是,比如有一个Person类,其有两个属性name和sex,有一个Student类是继承自Person类的,这个Student类自己有一个school属性。那么当我们使用runtime的方法读取Student类的属性列表时,只能读取到一个自己声明的属性school。但是实际上name和sex也是它的属性,所以这个时候就要遍历其父类,拿到所有的属性。

当我们拿到模型类的objc_property_t类型的属性时,就将其封装成MJProperty对象:

MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];

+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property方法先尝试从关联属性中通过property对象这个key来取出MJProperty对象,如果取不到就创建一个MJProperty对象,并通过property这个key将其与MJProperty的类对象关联起来,这样下次就可以直接通过关联属性来得到MJProperty的值了:

+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
    MJExtensionSemaphoreCreate
    MJExtensionSemaphoreWait
    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);
    }
    MJExtensionSemaphoreSignal
    return propertyObj;
}

然后再通过propertyObj.property = property;这行代码触发set方法,在set方法里面为MJProperty对象的name属性和type属性赋值,其中type属性就是和MJProperty对象关联的property属于什么类,是NSNumber类还是BOOL类等等:

- (void)setProperty:(objc_property_t)property
{
    _property = property;
    
    MJExtensionAssertParamNotNil(property);
    
    // 1.属性名
    _name = @(property_getName(property));
    
    // 2.成员类型
    NSString *attrs = @(property_getAttributes(property));
    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)];
    }
    _type = [MJPropertyType cachedTypeWithCode:code];
}

下面两行代码非常重要:

[property setOriginKey:[self propertyKey:property.name] forClass:self];
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];

对于第一行代码:
+ (id)propertyKey:(NSString *)propertyName这个方法是获取模型的属性名在字典中对应的key,什么意思呢?还是拿第二个例子来说,它有一个nameChangedTime属性,由于我们在模型类中实现了+ (NSDictionary *)mj_replacedKeyFromPropertyName这个方法,且这个方法中与nameChangedTime相对应的是name.info[1].nameChangedTime,所以+ (id)propertyKey:(NSString *)propertyName返回的就是name.info[1].nameChangedTime这个字符串。

对于- (void)setOriginKey:(id)originKey forClass:(Class)c方法,这个方法会把name.info[1].nameChangedTime这个字符串拆解成一段一段,并封装成一个个MJPropertyKey对象,组成数组,赋值给MJProperty的propertyKeysDict这个字典:

7C9F6395-B72F-425E-BA19-28D8DC66CA67.png

对于第二行代码
如果模型中有数组类型的属性,并且数组中的元素也是模型类,那么就需要在模型类中实现mj_objectClassInArray方法,就像下面这样:
模型类中有一个数组类型的属性statuses,数组中的元素类型是模型,模型类是Status;另一个数组类型的属性是ads,数组中的元素类型是模型,模型类是Ad。

+ (NSDictionary *)mj_objectClassInArray
{
    return @{
             @"statuses" : @"Status",
             @"ads" : @"Ad"
             };
}

这时如果在+ (Class)propertyObjectClassInArray:(NSString *)propertyName方法中传入statuses属性,那么返回的就是Status类。

3920D33D-5C12-4645-8F4D-E2E884D2C5A6.png
然后- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c方法将这个Status类对象赋值给MJProperty对象的objectClassInArrayDict字典。

到这里遍历类的所有属性就结束了,这样获得了整个类的所有属性,每个属性被封装成了一个MJProperty对象,MJProperty对象有一个property属性,还有type属性来表征这个属性属于什么类。此外MJProperty对象还保存着两个字典propertyKeysDictobjectClassInArrayDict,这两个字典的key都是NSStringFromClass(c),前者的value是一个数组,这个数组里面的元素是MJPropertyKey类型的,主要是用来取值用的,后者的value是一个类对象,如果属性是一个数组类型的属性,且数组元素是模型类型,那么这个值就是模型的类对象。

3.对模型进行赋值

首先如果这个属性不在属性白名单里或者在属性黑名单里,那么就返回,不对属性赋值:

if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;

然后从每个属性的propertyKeysDict字典中取出propertyKeys数组,根据propertyKeys数组来取值:

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)valueInObject:(id)object这个方法是怎么操作的:

0B08FD70-4C7A-4E0D-AE8C-97ED4C8B201A.png

如果属性的类型是可变的类型,而取出的value是不可变的类型,那么就要把不可变类型变换为可变的类型:

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

上面就是完成了对属性的第一步赋值,但是这还不够,如果这个属性是模型类型,那么还要对这个模型再进行一次字典转模型操作。如果这个属性是数组类型且数组元素是模型类型,那么还要进行字典数组转模型数组的操作。或者属性是NSURL类型,value是NSString类型,这样也要进行一下转换:

CFDDC68C-234B-4B63-903F-1B965273EC6C.png

这样整个模型赋值的过程也就完成了。

MJExtension中的一部分缓存操作

MJExtension中进行了大量的缓存操作来优化性能,下面讲几个比较重要的缓存,理解了这些缓存也有助于更深入的理解整个框架。

1.

NSObject+MJProperty这个分类中保存着一个字典cachedPropertiesDict,这个字典的keyNSStringFromClass(class),值就是一个数组,这个数组里面存放着一个类的所有属性。这样当我们下一次还要对同一个类进行模型赋值操作,就可以直接从这个字典里面取出这个类的一个包含所有属性的数组了。

2.

MJProperty这个类中,通过runtime的动态关联属性的方法,关联每一个objc_property_t,注意是与类对象相关联。value是MJProperty对象:

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

想象一种情况,Teacher和Student都继承自Person,所以Teacher和Student都有Person的属性,当我们先给Teacher模型赋值的时候,Person类的每一个属性已经调用了上面的代码块封装成了MJProperty对象,并与MJProperty类对象相关联。那么当我们再给Student模型赋值的时候,也会遍历Person类的属性,但是这个时候通过MJProperty *propertyObj = objc_getAssociatedObject(self, property);已经能得到MJProperty对象了,不用去创建。

3.

MJPropertyType中有一个types字典,这个字典是在单例中初始化的,types字典的key是code,value是MJPropertyType对象,每次有新的code,就添加到这个字典里面去,这样的好处就是如果code一致,就可以直接从字典中取MJPropertyType。

4.

每一个MJProperty对象都有一个propertyKeysDict字典,这个字典的key是NSStringFromClass(class),值是一个数组,比如一个MJProperty的名字是name.info[1].text,那么这个数组就会包括4个MJPropertyKey对象,分别表示name,info,1,text,这些key是在取值的时候用的。那么问题来了,为什么要设计字典来存储呢 ,直接用一个数组来存储不就好了吗?

其实这个问题和2相似,因为我们在第二次遍历Person类中的属性的时候不用去创建一个MJProperty对象,直接通过关联属性去取值就好了,但是Student模型和Teacher模型它们的propertyKeys是有可能不一样的,所以这里需要一个key来加以区分。

由于个人水平非常有限,这篇博客也只是我自己的理解,因此一定会有理解有误的地方,还请各位不吝指教。
这篇文章在简书的地址:MJExtension源码解读

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

推荐阅读更多精彩内容