MJExtension是是一個很是易用且功能強大的第三方Model和JSON相互轉化的商業化第三方庫,幫助開發者節省了從JSON或者Foundation object轉換成Model所需的時間,並且強大的拓展功能,知足了開發者的大部分數據模型化的需求。html
用MJ本身的話來講,第三方庫MJExtension就是git
A fast, convenient and nonintrusive conversion between JSON and model. Your model class don't need to extend another base class. You don't need to modify any model file.github
MJExtension項目源代碼請查看github上的Demo數組
下面,Fabric就來爲你們揭開MJExtension的神祕面紗。緩存
這篇文章適合於iOS中級開發者,在開啓閱讀以前,你須要瞭解如下知識點:微信
However,不管你了不瞭解這些知識點,相信讀完整篇文章都會幫助你更加深刻地瞭解Object-C這門語言,領略它獨特的魅力。數據結構
爲了方便敘述,我把一些基本的Foundation的數據結構,例如NSDictionary, NSArray, NSSet等統一稱之爲Foundation object。app
基本原理很是簡單,Fabric在這裏簡略介紹一下ide
unsigned int propertyCount = 0;
///經過運行時獲取當前類的屬性
objc_property_t *propertys = class_copyPropertyList([self class], &propertyCount);
//把屬性放到數組中
for (int i = 0; i < propertyCount; i ++) {
///取出第一個屬性
objc_property_t property = propertys[i];
//獲得屬性對應的名稱
NSString *name = @(property_getName(property));
NSLog(@"name:%@", name);
}
複製代碼
ps:雖然沒有看到runtime的完整源碼,可是有些方法的內部構造咱們仍是能夠猜想出來的,例如:函數
const char * _Nonnull property_getName(objc_property_t _Nonnull property) {
return property->name;
}
複製代碼
這就是一個對於獲取結構體對應指針值的一個很簡單的包裝。
- (void)setValue:(id)value forObject:(id)object
{
if (self.type.KVCDisabled || value == nil) return;
[object setValue:value forKey:self.name];
}
複製代碼
ps:這裏須要着重介紹一下,NSObject能夠經過-[setValue:forKey:]的方式對相應的屬性進行賦值,瞭解這點對於瞭解MJExtension原理頗有必要。
看了Fabric剛纔的基本原理介紹,你們可能認爲JSON轉化爲model很是簡單嘛,核心代碼也就幾句。可是,MJExtension做爲商業化SDK,它的強大優點在於它的兼容性好,拓展性強。開發者能夠替換key值的名稱,能夠將數組裏面的字典轉化爲對應的model,能夠忽略某些轉換的屬性,也能夠定義全部須要轉換的屬性,還能夠針對於一些舊值,轉換爲新值,例如時間戳和時間的相互轉換。
Fabric畫了一張原理圖,大體地將MJExtension的內部結構和類與類之間的相互關係描述了出來。
@encode(int)
@encode(float)
@encode(NSString)
打印出一些經常使用類型屬性的encode值來加深理解。+ (instancetype)cachedTypeWithCode:(NSString *)code
用於查找緩存的type類型。
BOOL idType
BOOL numberType
BOOL boolType
等一堆表明要轉換value對象具體類型的屬性。
Class typeClass
代表value對象的類型。
NSString *code
,用來寫入Property的encode值。
BOOL fromFoundation
,用來表示要轉換的 對象是不是NSDictionary``NSArray``NSSet
等基本的Foundation object類型,簡單來講就是若是要轉換的對象是NSObject的子類且不是NSManagedObject類就返回NO。
KVCDisabled
該對象是否能被監聽
- (id)valueInObject:(id)object
用於將value值寫入MJProperty對象。 MJPropertyKeyType type
用於代表當前的MJProperty中須要轉換的value是一個字典裏面的value仍是在數組裏面的value。 NSString *name
用於表示當前的NSDictionary中value的key值或者NSArray中value的index。-[encodeObject:forKey:]
和 -[decoderObject:forkey:]
兩個方法,使得對象能夠直接進行歸檔操做。MJExtension的設計很是巧妙,涉及的方法也很是多,有限的篇幅裏面很難說的細緻入微,因此Fabric決定帶你們一塊兒探索一下MJExtension實現Model轉換的核心的方法: - (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
。咱們按照方法裏面的代碼,由上之下,從內而外執行下去:
//將JSON轉換爲Foundation object對象(NSDictionary, NSArray等)
keyValues = [keyValues mj_JSONObject];
複製代碼
//設置黑名單和白名單
NSArray *allowedPropertyNames = [clazzmj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
複製代碼
//聲稱了全部的MJProperty屬性並進行遍歷輸出
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
//遍歷全部的MJProperty對象,設置到Model的對應的屬性當中
}
複製代碼
如今咱們探究一下,每個MJProperty對象是怎麼生成的,結合我上面的MJExtension內部結構圖,你們可能理解起來比較容易。 在NSObject+MJProperty
類中,+ (NSMutableArray *)properties
這個方法是專門負責生成全部的MJProperty對象的。
//首先從緩存中讀取存儲的MJProperty數組
NSMutableArray *cachedProperties = [self dictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
複製代碼
在這裏須要注意兩點:
一、掌握緩存的技巧能夠提升項目性能,減小代碼重複執行。
二、Fabric認爲這裏並不須要用字典來存儲MJproperty對象數組,由於每個Model都對應一個class,因此不存在兩個class公用一個cachedProperty的狀況,所以直接用數組來承接MJProperty屬性數組就能夠了。
繼續探究+ (NSMutableArray *)properties
方法, 若是沒有緩存,就遍歷全部的非Foundation object基本類型的對象,取出objc_property_t數組,包裝成MJProperty數組。
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(c, &outCount);
for (unsigned int i = 0; i<outCount; i++) {
//包裝properties
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
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];
}
複製代碼
Fabric認爲
if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
property.srcClass = c;
複製代碼
這兩行代碼有些雞肋,由於srcClass只有兩種可能:Model類型或者nil,因此徹底可使用一個BOOL值來判斷srcClass是不是Foundation object類型,而不用一個Class *srcClass
屬性。
在這裏須要着重理解這幾行代碼
[property setOriginKey:[self propertyKey:property.name] forClass:self];
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name]
複製代碼
第一個方法是把全部要替換的key值包裝成數組存儲到NSMutableDictionary *propertyKeysDict
對象中;
第二個方法是把數組中對應的想要轉換成的Model的Class類型保存到NSMutableDictionary *objectClassInArrayDict
字典中。
理解了這兩個方法也就理解了+ (NSDictionary *)mj_replacedKeyFromPropertyName
和+ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray
兩個功能函數的實現原理了。
繼續看如何包裝objc_property_t property
,先從緩存中讀取MJProperty。
//這裏須要注意property指針指向的內存地址每次都是不變的,因此能夠這樣動態關聯
MJProperty *propertyObj = objc_getAssociatedObject(self, property);
複製代碼
若是沒有緩存就把property包裝成一個MJProperty,
- (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的name和attrs中的encode屬性經過截取字符串來得到屬性的類型。 下面來看一下MJProperty的type屬性是如何設置的,
#pragma mark - 公共方法
- (void)setCode:(NSString *)code
{
_code = code;
MJExtensionAssertParamNotNil(code);
if ([code isEqualToString:MJPropertyTypeId]) {
_idType = YES;
} else if (code.length == 0) {
_KVCDisabled = YES;
} else if (code.length > 3 && [code hasPrefix:@"@\""]) {
// 去掉@"和",截取中間的類型名稱
_code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
_typeClass = NSClassFromString(_code);
_fromFoundation = [MJFoundation isClassFromFoundation:_typeClass];
_numberType = [_typeClass isSubclassOfClass:[NSNumber class]];
} else if ([code isEqualToString:MJPropertyTypeSEL] ||
[code isEqualToString:MJPropertyTypeIvar] ||
[code isEqualToString:MJPropertyTypeMethod]) {
_KVCDisabled = YES;
}
// 是否爲數字類型
NSString *lowerCode = _code.lowercaseString;
NSArray *numberTypes = @[MJPropertyTypeInt, MJPropertyTypeShort, MJPropertyTypeBOOL1, MJPropertyTypeBOOL2, MJPropertyTypeFloat, MJPropertyTypeDouble, MJPropertyTypeLong, MJPropertyTypeLongLong, MJPropertyTypeChar];
if ([numberTypes containsObject:lowerCode]) {
_numberType = YES;
if ([lowerCode isEqualToString:MJPropertyTypeBOOL1]
|| [lowerCode isEqualToString:MJPropertyTypeBOOL2]) {
_boolType = YES;
}
}
}
複製代碼
以上代碼判斷了property屬性的具體類型,參考@encode()函數和MJExtensionConst方法,你們應該可以理解以上代碼。
到這裏,MJProperty的包裝就基本說完了。
緊接着,取出keyValues中對應的值,
// 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;
}
複製代碼
處理value值,
// 值的過濾
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;
複製代碼
最後是根據MJProperty的type中的typeClass,將不可擴展的集合轉換成可變集合,方便後續的操做。對特定的值進行處理,若是是模型屬性就繼續進行遞歸轉化,
if (!type.isFromFoundation && propertyClass) { // 模型屬性
value = [propertyClass mj_objectWithKeyValues:value context:context];
}
複製代碼
若是是數組,就遍歷數組,若是數組中仍是數組就繼續遞歸,若是不是調用+[mj_objectWithKeyValues:context]轉化成對應的Model元素放入數組,
for (NSDictionary *keyValues in keyValuesArray) {
if ([keyValues isKindOfClass:[NSArray class]]){
[modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];
} else {
id model = [self mj_objectWithKeyValues:keyValues context:context];
if (model) [modelArray addObject:model];
}
}
複製代碼
對於其餘的propertyClass類型,Fabric在這裏不作贅述,你們能夠本身研究,並不複雜。
最後一步,將通過處理的value值代入Model當中,
// 3.賦值
[property setValue:value forObject:self];
複製代碼
在模型轉換完成以後,你們還能夠重寫- (void)mj_keyValuesDidFinishConvertingToObject
這個函數進行後續操做。
說到這裏,Fabric基本上把MJExtension的Foundation object(或者JSON)轉換成Model的原理細緻的說了一遍。由於篇幅有限,本人能力有限,不少東西沒有說清楚,感興趣的同窗和朋友們能夠加個人微信:justlikeitRobert進行詳細探討。
謝謝你們的耐心閱讀,Fabric祝你們狗年旺旺旺!