内容提要: MJExtension是一套字典和模型之间互相转换的超轻量级框架。字典转model是最典型的一个运用场景,我对MJExtension的理解是:
1.首先是对已经建立的model通过运行时取出该model的属性编入一个属性数组;
2.然后是对该属性数组进行遍历,在该过程中,取出相应属性值的字典的value值。
3.把取出来的value值赋值给model的匹配属性。
4.遍历结束即赋值结束,也就是字典转model结束。
5.MJExtension做的比较好的地方是对数据的处理做到了极致,和容错判断的处理。其中数据的处理包括对模型属性值的类型的判断和对字典每个value类型的判断的处理。
本篇进行简单的字典转model的解读,其他的转换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.将字典转为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中复制得到,源码解读从mj_objectWithKeyValues:
开始。
点击该方法进入实现:
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
return [self mj_objectWithKeyValues:keyValues context:nil];
}
在这里context参数传nil,其中NSManagedObjectContext是:一个对于数据库的封装,只要能保存在数据库中的内容,都可以保存在NSMangedObjectContext中。它的地址是通过NSPersistentStoreCoordinator定义的,一般存放在应用程序的Document目录下。
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典");
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
方法实现
- (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:
方法实现转为json对象;因为本演示demo是一个字典,故会执行mj_keyValues方法。该方法实现如下:
#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];
...//此处省略了模型转字典的具体实现。
return keyValues;
}
其中MJExtensionAssertError
是一个断言,具体定义为:
#define MJExtensionAssertError(condition, returnValue, clazz, msg) \
[clazz setMj_error:nil]; \
if ((condition) == NO) { \
MJExtensionBuildError(clazz, msg); \
return returnValue;\
}
二.断言判断
用MJExtensionAssertError
断言判断该keyValues参数是否是一个字典。如果不是一个字典,则返回nil。
三.判断上下文
在这里该类不是NSManagedObject对象没并且NSManagedObjectContext参数传的也是nil,所以不会进入此判断。
四.创建模型
mj_setKeyValues:
实现如下:
#pragma mark - 字典 -> 模型
- (instancetype)mj_setKeyValues:(id)keyValues
{
return [self mj_setKeyValues:keyValues context:nil];
}
接下来重点来了,看看字典是怎么转为模型的:
/**
核心代码:
*/
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
//判断是否是字典,如果不是一个字典的话,直接返回self。
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
...
//通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
[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.复杂处理
...
// 3.赋值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
// 转换完毕
...
return self;
}
核心代码的思路很清晰:
1.先获模型取所有的属性,mj_enumerateProperties:
是一个枚举block,在里边执行具体给model赋值的工作。
2.当具体执行每一个属性的时候,先检测是否被忽略,若是被忽略,则直接return跳出该循环,进行写一个属性赋值。
3.取出属性值,如果没有值,直接返回,不进行下列步骤。
4.复杂处理:处理属性是什么类型,对应的value的处理。
5进行赋值:针对复杂处理过的value对model进行赋值,经过这一步,该model的赋值过的属性已经是有值了。
6.转换完毕,返回model自身。
总结:字典转model是最典型的一个运用场景,我对MJExtension的理解是:
1.首先是对已经建立的model通过运行时取出该model的属性编入一个属性数组;
2.然后是对该属性数组进行遍历,在该过程中,取出相应属性值的字典的value值。
3.把取出来的value值赋值给model的属性。
4.遍历结束即赋值结束,也就是字典转model结束。
5.MJExtension做的比较好的地方是对数据的处理做到了极致,和容错判断的处理。其中数据的处理包括对模型属性值的类型的判断和对字典每个value类型的判断的处理。
有关思考:
1.利用MJExtension能否对有只读属性的model进行赋值?
答:可以,我作了一下测试,我对例子中的模型MJUser的name属性添加了一个readonly
修饰,变成了只读属性。因为是只读属性,明文操作像
MJUser *user = [[MJUser alloc] init];
user.name = @"Jack";//会提示Assignment to readonly property
意思是只读属性,无法在外部进行赋值。
还是文章开头的例子打印结果如下图:
由打印结果看出:只读属性是可以用runtime进行赋值的。
MJExtension源码地址:https://github.com/CoderMJLee/MJExtension