內(nèi)容提要: MJExtension是一套字典和模型之間互相轉(zhuǎn)換的超輕量級框架。字典轉(zhuǎn)model是最典型的一個運用場景刻恭,我對MJExtension的理解是:
1.首先是對已經(jīng)建立的model通過運行時取出該model的屬性編入一個屬性數(shù)組;
2.然后是對該屬性數(shù)組進行遍歷扯夭,在該過程中鳍贾,取出相應(yīng)屬性值的字典的value值。
3.把取出來的value值賦值給model的匹配屬性交洗。
4.遍歷結(jié)束即賦值結(jié)束骑科,也就是字典轉(zhuǎn)model結(jié)束。
5.MJExtension做的比較好的地方是對數(shù)據(jù)的處理做到了極致构拳,和容錯判斷的處理咆爽。其中數(shù)據(jù)的處理包括對模型屬性值的類型的判斷和對字典每個value類型的判斷的處理梁棠。
本篇進行簡單的字典轉(zhuǎn)model的解讀,其他的轉(zhuǎn)換model方法會在下一篇給出解釋斗埂。
參看如下事例:
// 1.定義一個字典
NSDictionary *dict = @{
@"name" : @"Jack",
@"icon" : @"lufy.png",
@"age" : @"20",
@"height" : @1.55,
@"money" : @"100.9",
@"sex" : @(SexFemale),
@"gay" : @"1"
// @"gay" : @"NO"
// @"gay" : @"true"
};
// 2.將字典轉(zhuǎn)為MJUser模型
MJUser *user = [MJUser mj_objectWithKeyValues:dict];
// 3.打印MJUser模型的屬性
MJExtensionLog(@"name=%@, icon=%@, age=%zd, height=%@, money=%@, sex=%d, gay=%d", user.name, user.icon, user.age, user.height, user.money, user.sex, user.gay);
事例是直接從demo中復(fù)制得到符糊,源碼解讀從mj_objectWithKeyValues:
開始。
點擊該方法進入實現(xiàn):
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
return [self mj_objectWithKeyValues:keyValues context:nil];
}
在這里context參數(shù)傳nil,其中NSManagedObjectContext是:一個對于數(shù)據(jù)庫的封裝呛凶,只要能保存在數(shù)據(jù)庫中的內(nèi)容濒蒋,都可以保存在NSMangedObjectContext中。它的地址是通過NSPersistentStoreCoordinator定義的把兔,一般存放在應(yīng)用程序的Document目錄下。
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得JSON對象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues參數(shù)不是一個字典");
if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
}
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
一.先獲得json對象
針對mj_JSONObject
方法實現(xiàn)
- (id)mj_JSONObject
{
if ([self isKindOfClass:[NSString class]]) {
return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
} else if ([self isKindOfClass:[NSData class]]) {
return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
}
return self.mj_keyValues;
}
判斷該對象是否是NSString對象或NSData對象瓮顽,若是其中一個县好,則中NSJSONSerialization的+ (nullable id)JSONObjectWithData: options: error:
方法實現(xiàn)轉(zhuǎn)為json對象;因為本演示demo是一個字典暖混,故會執(zhí)行mj_keyValues方法缕贡。該方法實現(xiàn)如下:
#pragma mark - 模型 -> 字典
- (NSMutableDictionary *)mj_keyValues
{
return [self mj_keyValuesWithKeys:nil ignoredKeys:nil];
}
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
{
// 如果自己不是模型類, 那就返回自己
MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定義的模型類")
id keyValues = [NSMutableDictionary dictionary];
...//此處省略了模型轉(zhuǎn)字典的具體實現(xiàn)。
return keyValues;
}
其中MJExtensionAssertError
是一個斷言拣播,具體定義為:
#define MJExtensionAssertError(condition, returnValue, clazz, msg) \
[clazz setMj_error:nil]; \
if ((condition) == NO) { \
MJExtensionBuildError(clazz, msg); \
return returnValue;\
}
二.斷言判斷
用MJExtensionAssertError
斷言判斷該keyValues參數(shù)是否是一個字典晾咪。如果不是一個字典,則返回nil贮配。
三.判斷上下文
在這里該類不是NSManagedObject對象沒并且NSManagedObjectContext參數(shù)傳的也是nil,所以不會進入此判斷谍倦。
四.創(chuàng)建模型
mj_setKeyValues:
實現(xiàn)如下:
#pragma mark - 字典 -> 模型
- (instancetype)mj_setKeyValues:(id)keyValues
{
return [self mj_setKeyValues:keyValues context:nil];
}
接下來重點來了,看看字典是怎么轉(zhuǎn)為模型的:
/**
核心代碼:
*/
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得JSON對象
keyValues = [keyValues mj_JSONObject];
//判斷是否是字典泪勒,如果不是一個字典的話昼蛀,直接返回self。
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個字典");
...
//通過封裝的方法回調(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;
}
// 值的過濾
...
// 如果沒有值,就直接返回
if (!value || value == [NSNull null]) return;
// 2.復(fù)雜處理
...
// 3.賦值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
// 轉(zhuǎn)換完畢
...
return self;
}
核心代碼的思路很清晰:
1.先獲模型取所有的屬性沦辙,mj_enumerateProperties:
是一個枚舉block,在里邊執(zhí)行具體給model賦值的工作夫植。
2.當(dāng)具體執(zhí)行每一個屬性的時候,先檢測是否被忽略油讯,若是被忽略详民,則直接return跳出該循環(huán),進行寫一個屬性賦值撞羽。
3.取出屬性值阐斜,如果沒有值,直接返回诀紊,不進行下列步驟谒出。
4.復(fù)雜處理:處理屬性是什么類型隅俘,對應(yīng)的value的處理。
5進行賦值:針對復(fù)雜處理過的value對model進行賦值笤喳,經(jīng)過這一步为居,該model的賦值過的屬性已經(jīng)是有值了。
6.轉(zhuǎn)換完畢杀狡,返回model自身蒙畴。
總結(jié):字典轉(zhuǎn)model是最典型的一個運用場景,我對MJExtension的理解是:
1.首先是對已經(jīng)建立的model通過運行時取出該model的屬性編入一個屬性數(shù)組呜象;
2.然后是對該屬性數(shù)組進行遍歷膳凝,在該過程中,取出相應(yīng)屬性值的字典的value值恭陡。
3.把取出來的value值賦值給model的屬性蹬音。
4.遍歷結(jié)束即賦值結(jié)束,也就是字典轉(zhuǎn)model結(jié)束休玩。
5.MJExtension做的比較好的地方是對數(shù)據(jù)的處理做到了極致著淆,和容錯判斷的處理。其中數(shù)據(jù)的處理包括對模型屬性值的類型的判斷和對字典每個value類型的判斷的處理拴疤。
有關(guān)思考:
1.利用MJExtension能否對有只讀屬性的model進行賦值永部?
答:可以,我作了一下測試呐矾,我對例子中的模型MJUser的name屬性添加了一個readonly
修飾苔埋,變成了只讀屬性。因為是只讀屬性蜒犯,明文操作像
MJUser *user = [[MJUser alloc] init];
user.name = @"Jack";//會提示Assignment to readonly property
意思是只讀屬性讲坎,無法在外部進行賦值。
還是文章開頭的例子打印結(jié)果如下圖:
由打印結(jié)果看出:只讀屬性是可以用runtime進行賦值的愧薛。
MJExtension源碼地址:https://github.com/CoderMJLee/MJExtension