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方法:
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
類型的枚舉值冠场,這個枚舉值有兩種類型家浇,即MJPropertyKeyTypeDictionary
和MJPropertyKeyTypeArray
,像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這個字典:
對于第二行代碼
如果模型中有數(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類笆凌。
- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c
方法將這個Status類對象賦值給MJProperty對象的objectClassInArrayDict
字典。
到這里遍歷類的所有屬性就結(jié)束了士葫,這樣獲得了整個類的所有屬性乞而,每個屬性被封裝成了一個MJProperty對象,MJProperty對象有一個property屬性慢显,還有type屬性來表征這個屬性屬于什么類爪模。此外MJProperty對象還保存著兩個字典
propertyKeysDict
和objectClassInArrayDict
,這兩個字典的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
這個方法是怎么操作的:
如果屬性的類型是可變的類型,而取出的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)換:
這樣整個模型賦值的過程也就完成了坛怪。
MJExtension中的一部分緩存操作
MJExtension中進行了大量的緩存操作來優(yōu)化性能,下面講幾個比較重要的緩存股囊,理解了這些緩存也有助于更深入的理解整個框架袜匿。
1.
NSObject+MJProperty
這個分類中保存著一個字典cachedPropertiesDict
,這個字典的key
是NSStringFromClass(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源碼解讀