MJExtension原始碼解讀

雪山飛狐1發表於2018-11-29

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原始碼解讀

相關文章