最近这几天看了MJExtension的源码,虽然不是每一行代码都读了,但是核心功能部分的代码都看了一遍,大致知道了实现原理,并且学习到了一些新的知识。在这里做一下记录。
首先根据代码从头到尾的运行解释一下。
只做iOS的同学可能不知道程序是在哪里执行的,并没有写在ViewController和AppDelegate里面,而是写在了main文件里面,并且用的是C函数。
execute(keyValues2object, @"简单的字典 -> 模型");
execute(keyValues2object1, @"JSON字符串 -> 模型");
execute(keyValues2object2, @"复杂的字典 -> 模型 (模型里面包含了模型)");
execute(keyValues2object3, @"复杂的字典 -> 模型 (模型的数组属性里面又装着模型)");
execute(keyValues2object4, @"简单的字典 -> 模型(key替换,比如ID和id,支持多级映射)");
execute(keyValuesArray2objectArray, @"字典数组 -> 模型数组");
execute(object2keyValues, @"模型转字典");
execute(objectArray2keyValuesArray, @"模型数组 -> 字典数组");
execute(coreData, @"CoreData示例");
execute(coding, @"NSCoding示例");
execute(replacedKeyFromPropertyName121, @"统一转换属性名(比如驼峰转下划线)");
execute(newValueFromOldValue, @"过滤字典的值(比如字符串日期处理为NSDate、字符串nil处理为@"")");
execute(logAllProperties, @"使用MJExtensionLog打印模型的所有属性");
想运行哪个就注释掉其他的就好了。
最简单的用法:
// 1.定义一个字典
NSDictionary *dict = @{
@"name" : @"Jack",
@"icon" : @"lufy.png",
@"age" : @"20",
@"height" : @1.55,
@"money" : @"100.9",
@"sex" : @(SexFemale),
@"gay" : @"1"
};
// 2.将字典转为MJUser模型
MJUser *user = [MJUser mj_objectWithKeyValues:dict];
其实这里并不只是字典,包括data和string都可以,往下看(为了便于理解,先不管CoreData的部分)。
它把传进去的参数先做了这样一个操作。
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;
kNilOptions表示0,就是不用任何选项。在细微的地方提高效率,学到了。
这就保证了,不管你穿的是string还是data,最终都会被转化为字典(但是不能转化的最后会有断言来报错)。
下面来到了核心代码,首先获取白名单和黑名单。
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
它使用协议的方式,需要在要转化的对象里面实现它定义的方法,并返回白名单或者黑名单数组。
获取成员变量。
这里使用字典的方式做了缓存,如果有缓存就直接取。没有进入下一步(这里的字典是用的static变量放在外面的)。
下面他用了一个循环,对这个类以及他的父类执行block操作。
// 1.没有block就直接返回
if (enumeration == nil) return;
// 2.停止遍历的标记
BOOL stop = NO;
// 3.当前正在遍历的类
Class c = self;
// 4.开始遍历每一个类
while (c && !stop) {
// 4.1.执行操作
enumeration(c, &stop);
// 4.2.获得父类
c = class_getSuperclass(c);
if ([MJFoundation isClassFromFoundation:c]) break;
}
看这个block操作。
objc_property_t *properties = class_copyPropertyList(c, &outCount);
使用这个方法可以获取这个类的所有属性。
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
这里对每一个属性又做了缓存,上面都做了为什么这里还要做呢。
因为上面说了是要遍历父类的,所以当对这个父类的另一个子类做操作的时候就可以节省掉便利父类的时间了。
这个缓存因为要根据类和属性地址获取属性,所以用字典就不太合适的,因此这里使用了关联。
MJProperty *propertyObj = objc_getAssociatedObject(self, property);
来获取属性。MJProperty是一个自定义类,用来存储属性的各种信息,包括objc_property_t,属性名称,属性类型等等.
看到下面一句代码的时候,千万不要以为就是一个直接赋值。
propertyObj.property = property;
因为他重写了set方法。。。
在重写的方法里他获取了property的attribute,通过这个可以获取变量的类型,这快怎么获取的自己看源码吧(涉及到了int,long,bool等类型应该怎么办)。。。
上面,他就获取到了model的全部变量信息.
然后对每一个变量,做下面的操作。
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;
检查白名单和黑名单。
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;
安全性检查。
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;
}
}
先是对string类型的转化,然后是对number类型的转化。
如果最后类型不匹配就赋值为nil。
最后用kvc赋值。
使用这个东西比自己写强多了,节省时间,还有各种各样的自动转换,安全性检查,推荐大家使用。
下一步打算有时间看看YYModel,看了下描述转化效率很高而且得到一致的好评,代码很低层,读起来应该很有挑战。