MJExtension源碼解讀

MJExtension

A fast, convenient and nonintrusive conversion framework between JSON and model.
轉(zhuǎn)換速度快粒蜈、使用簡單方便的字典轉(zhuǎn)模型框架

我們經(jīng)常需要從網(wǎng)絡(luò)上拉取json數(shù)據(jù)患膛,然后將json數(shù)據(jù)轉(zhuǎn)化為自己的模型數(shù)據(jù)岖沛,將json數(shù)據(jù)轉(zhuǎn)化為我們自己的模型數(shù)據(jù)經(jīng)常使用的框架有YYModel和MJExtension,所以現(xiàn)在也是打算花一些時間看一下MJExtension的源碼许布,并且寫一篇博客記錄一下,因為不記錄下來的話感覺很容易忘,學(xué)習(xí)效果不佳粮彤。

使用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

字典轉(zhuǎn)模型:

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

打印結(jié)果:

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

通過一句簡單的代碼,就把字典數(shù)據(jù)轉(zhuǎn)化為了模型數(shù)據(jù)姜骡,非常方便簡潔导坟。

復(fù)雜一點的應(yīng)用

很多時候json轉(zhuǎn)模型都不是這樣簡單。有時候會出現(xiàn)模型中嵌套模型或者模型中的屬性名和json數(shù)據(jù)中的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數(shù)據(jù):

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

可以看到字典數(shù)據(jù)中是id惫周,而模型中是ID,同樣也有desc和description康栈。模型中有newName和oldName這些屬性递递,而字典中這些屬性在name字段下面。bag屬性也是一樣的道理啥么,那么怎么辦呢登舞?
我們只需要實現(xiàn)MJExtension中的+ (NSDictionary *)mj_replacedKeyFromPropertyName方法,在Student.m中#import <MJExtension.h>然后實現(xiàn)+ (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"
             };
}

這個方法的作用就是在給模型賦值的時候悬荣,把右邊字段的值賦給模型中左邊字段的屬性菠秒。
轉(zhuǎn)化一下試試:

// 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

這個地方需要關(guān)注一個地方就是模型中的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);

打印結(jié)果:

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對象來創(chuàng)建一個對應(yīng)的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去緩存中取譬猫,如果緩存中取不到讯檐,那么就根據(jù)objc_property_t來創(chuàng)建一個MJProperty對象,并且把這個MJProperty對象通過property這個key與MJProperty類對象關(guān)聯(lián)起來染服。那么下次如果再從緩存中取同一個objc_property_t對應(yīng)的MJProperty對象就能取到了别洪,就不用再創(chuàng)建了。這也是MJ大神使用緩存的一個地方柳刮。
上面代碼塊中propertyObj.property = property;這行代碼觸發(fā)了MJProperty對象的set方法:

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

MJProperty有一個type屬性挖垛,這個屬性是MJPropertyType類的,就是表示MJProperty對象的property屬性是屬于什么類型的秉颗。

另外每一個MJProperty對象還持有著兩個字典痢毒,一個是propertyKeysDict,一個是objectClassInArrayDict蚕甥。

  • propertyKeysDict
    這個字典的key是NSStringFromClass(class)哪替,值是一個數(shù)組,比如在復(fù)雜一點的應(yīng)用中菇怀,給模型中的nameChangedTime這個屬性賦值的時候夷家,在字典中去取值的時候要對應(yīng)name.info[1].nameChangedTime這個字段的值。那么就要把name敏释,info,1摸袁,nameChangedTim钥顽,這個四個字段分別封裝為一個MJPropertyKey,加入一個數(shù)組中靠汁,作為value蜂大。這個數(shù)組在最終取值的時候會用到闽铐。
  • objectClassInArrayDict
    這個字典的key也是NSStringFromClass(class),值是一個類對象奶浦,表示如果這個MJProperty對象的類型是數(shù)組兄墅,并且數(shù)組中的元素類型是模型,那么這個個字典的value就是模型的類對象澳叉。
MJPropertyKey

上面說過隙咸,給模型中的nameChangedTime這個屬性賦值的時候,在字典中取值的時候要對應(yīng)name.info[1].nameChangedTime這個字段的值成洗,那么就要把name五督,info,1瓶殃,nameCHangedTime這四個字段分別封裝成一個MJPropertyKey充包。

它有兩個屬性,一個屬性是name遥椿,也就是name,info基矮,1這種,還有一個就是type它是自定義的MJPropertyKeyType類型的枚舉值冠场,這個枚舉值有兩種類型家浇,即MJPropertyKeyTypeDictionaryMJPropertyKeyTypeArray,像name慈鸠,info這種就屬于MJPropertyKeyTypeDictionary類型的蓝谨,1就屬于MJPropertyKeyTypeArray類型的。這個也是在取值的時候用的青团,類型是MJPropertyKeyTypeDictionary就是從字典中取值譬巫,類型是MJPropertyKeyTypeArray就是從數(shù)組中取值。

MJPropertyType

MJProperty類有一個屬性是type督笆,這個屬性是MJPropertyType類的芦昔,這個type屬性就是表征這個MJProperty對象它的property屬性屬于什么類,NSString類或者NSNumber類等等娃肿。
MJProperty對象的type是通過截取property的attributes得到code然后初始化為MJPropertyType對象得到的:

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

核心源碼分析

我們就從復(fù)雜一點的應(yīng)用這個例子去看一下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參數(shù)不是一個字典");
    
    Class clazz = [self class];
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
    
    //通過封裝的方法回調(diào)一個通過運行時編寫的,用于返回屬性列表的方法料扰。
    [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.復(fù)雜處理
            MJPropertyType *type = property.type;
            Class propertyClass = type.typeClass;
            Class objectClass = [property objectClassInArrayForClass:[self class]];//模型數(shù)組中對象的類
            
            // 不可變 -> 可變處理
            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 { // 字典數(shù)組-->模型數(shù)組
                    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
                        // 字符串轉(zhuǎn)碼
                        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) {
                            // 字符串轉(zhuǎn)BOOL(字符串沒有charValue方法)
                            // 系統(tǒng)會調(diào)用字符串的charValue轉(zhuǎn)為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);
        }
    }];
    
    // 轉(zhuǎn)換完畢
    if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
        [self mj_keyValuesDidFinishConvertingToObject];
    }
    return self;
}

這一部分代碼很長,我們一部分一部分來看:

1.將json數(shù)據(jù)轉(zhuǎn)化為foundation類型:
    // 獲得JSON對象
    keyValues = [keyValues mj_JSONObject];
    
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個字典");
    
    Class clazz = [self class];
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];

allowedPropertyNames是允許進行字典和模型轉(zhuǎn)換的屬性名數(shù)組晒杈,ignoredPropertyNames是不允許進行字典和模型轉(zhuǎn)換額屬性名數(shù)組嫂伞,這兩個數(shù)組一般都是自己在模型類的.m文件中去設(shè)置的。

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.釋放內(nèi)存
      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方法先嘗試從關(guān)聯(lián)屬性中通過property對象這個key來取出MJProperty對象变抽,如果取不到就創(chuàng)建一個MJProperty對象,并通過property這個key將其與MJProperty的類對象關(guān)聯(lián)起來氮块,這樣下次就可以直接通過關(guān)聯(lián)屬性來得到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;這行代碼觸發(fā)set方法绍载,在set方法里面為MJProperty對象的name屬性和type屬性賦值,其中type屬性就是和MJProperty對象關(guān)聯(lián)的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這個方法是獲取模型的屬性名在字典中對應(yīng)的key击儡,什么意思呢?還是拿第二個例子來說蝠引,它有一個nameChangedTime屬性阳谍,由于我們在模型類中實現(xiàn)了+ (NSDictionary *)mj_replacedKeyFromPropertyName這個方法,且這個方法中與nameChangedTime相對應(yīng)的是name.info[1].nameChangedTime螃概,所以+ (id)propertyKey:(NSString *)propertyName返回的就是name.info[1].nameChangedTime這個字符串矫夯。

對于- (void)setOriginKey:(id)originKey forClass:(Class)c方法,這個方法會把name.info[1].nameChangedTime這個字符串拆解成一段一段吊洼,并封裝成一個個MJPropertyKey對象训貌,組成數(shù)組,賦值給MJProperty的propertyKeysDict這個字典:

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

對于第二行代碼
如果模型中有數(shù)組類型的屬性冒窍,并且數(shù)組中的元素也是模型類递沪,那么就需要在模型類中實現(xiàn)mj_objectClassInArray方法,就像下面這樣:
模型類中有一個數(shù)組類型的屬性statuses综液,數(shù)組中的元素類型是模型款慨,模型類是Status;另一個數(shù)組類型的屬性是ads谬莹,數(shù)組中的元素類型是模型檩奠,模型類是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字典。

到這里遍歷類的所有屬性就結(jié)束了士葫,這樣獲得了整個類的所有屬性乞而,每個屬性被封裝成了一個MJProperty對象,MJProperty對象有一個property屬性慢显,還有type屬性來表征這個屬性屬于什么類爪模。此外MJProperty對象還保存著兩個字典propertyKeysDictobjectClassInArrayDict,這兩個字典的key都是NSStringFromClass(c)荚藻,前者的value是一個數(shù)組屋灌,這個數(shù)組里面的元素是MJPropertyKey類型的,主要是用來取值用的应狱,后者的value是一個類對象共郭,如果屬性是一個數(shù)組類型的屬性,且數(shù)組元素是模型類型疾呻,那么這個值就是模型的類對象除嘹。

3.對模型進行賦值

首先如果這個屬性不在屬性白名單里或者在屬性黑名單里,那么就返回岸蜗,不對屬性賦值:

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

然后從每個屬性的propertyKeysDict字典中取出propertyKeys數(shù)組尉咕,根據(jù)propertyKeys數(shù)組來取值:

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]];//模型數(shù)組中對象的類
            
 // 不可變 -> 可變處理
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];
            }

上面就是完成了對屬性的第一步賦值年缎,但是這還不夠,如果這個屬性是模型類型铃慷,那么還要對這個模型再進行一次字典轉(zhuǎn)模型操作单芜。如果這個屬性是數(shù)組類型且數(shù)組元素是模型類型,那么還要進行字典數(shù)組轉(zhuǎn)模型數(shù)組的操作枚冗』航Γ或者屬性是NSURL類型,value是NSString類型赁温,這樣也要進行一下轉(zhuǎn)換:

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

這樣整個模型賦值的過程也就完成了坛怪。

MJExtension中的一部分緩存操作

MJExtension中進行了大量的緩存操作來優(yōu)化性能,下面講幾個比較重要的緩存股囊,理解了這些緩存也有助于更深入的理解整個框架袜匿。

1.

NSObject+MJProperty這個分類中保存著一個字典cachedPropertiesDict,這個字典的keyNSStringFromClass(class)稚疹,值就是一個數(shù)組居灯,這個數(shù)組里面存放著一個類的所有屬性祭务。這樣當我們下一次還要對同一個類進行模型賦值操作,就可以直接從這個字典里面取出這個類的一個包含所有屬性的數(shù)組了怪嫌。

2.

MJProperty這個類中义锥,通過runtime的動態(tài)關(guān)聯(lián)屬性的方法,關(guān)聯(lián)每一個objc_property_t岩灭,注意是與類對象相關(guān)聯(lián)拌倍。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類的每一個屬性已經(jīng)調(diào)用了上面的代碼塊封裝成了MJProperty對象找爱,并與MJProperty類對象相關(guān)聯(lián)梗顺。那么當我們再給Student模型賦值的時候,也會遍歷Person類的屬性车摄,但是這個時候通過MJProperty *propertyObj = objc_getAssociatedObject(self, property);已經(jīng)能得到MJProperty對象了寺谤,不用去創(chuàng)建。

3.

MJPropertyType中有一個types字典练般,這個字典是在單例中初始化的矗漾,types字典的key是code,value是MJPropertyType對象薄料,每次有新的code敞贡,就添加到這個字典里面去,這樣的好處就是如果code一致摄职,就可以直接從字典中取MJPropertyType誊役。

4.

每一個MJProperty對象都有一個propertyKeysDict字典,這個字典的key是NSStringFromClass(class)谷市,值是一個數(shù)組蛔垢,比如一個MJProperty的名字是name.info[1].text,那么這個數(shù)組就會包括4個MJPropertyKey對象迫悠,分別表示name,info,1,text,這些key是在取值的時候用的鹏漆。那么問題來了,為什么要設(shè)計字典來存儲呢 创泄,直接用一個數(shù)組來存儲不就好了嗎艺玲?

其實這個問題和2相似,因為我們在第二次遍歷Person類中的屬性的時候不用去創(chuàng)建一個MJProperty對象鞠抑,直接通過關(guān)聯(lián)屬性去取值就好了饭聚,但是Student模型和Teacher模型它們的propertyKeys是有可能不一樣的,所以這里需要一個key來加以區(qū)分搁拙。

由于個人水平非常有限秒梳,這篇博客也只是我自己的理解法绵,因此一定會有理解有誤的地方,還請各位不吝指教酪碘。
這篇文章在簡書的地址:MJExtension源碼解讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朋譬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子兴垦,更是在濱河造成了極大的恐慌此熬,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滑进,死亡現(xiàn)場離奇詭異,居然都是意外死亡募谎,警方通過查閱死者的電腦和手機扶关,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來数冬,“玉大人节槐,你說我怎么就攤上這事」丈矗” “怎么了铜异?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秸架。 經(jīng)常有香客問我揍庄,道長,這世上最難降的妖魔是什么东抹? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任蚂子,我火速辦了婚禮,結(jié)果婚禮上缭黔,老公的妹妹穿的比我還像新娘食茎。我一直安慰自己,他們只是感情好馏谨,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布别渔。 她就那樣靜靜地躺著,像睡著了一般惧互。 火紅的嫁衣襯著肌膚如雪哎媚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天壹哺,我揣著相機與錄音抄伍,去河邊找鬼。 笑死管宵,一個胖子當著我的面吹牛截珍,可吹牛的內(nèi)容都是我干的攀甚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼岗喉,長吁一口氣:“原來是場噩夢啊……” “哼秋度!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钱床,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤荚斯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后查牌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體事期,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年纸颜,在試婚紗的時候發(fā)現(xiàn)自己被綠了兽泣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡胁孙,死狀恐怖唠倦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涮较,我是刑警寧澤稠鼻,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站狂票,受9級特大地震影響候齿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闺属,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一毛肋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屋剑,春花似錦润匙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巍膘,卻和暖如春厂财,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峡懈。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工璃饱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肪康。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓荚恶,卻偏偏與公主長得像撩穿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谒撼,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容