MJExtension 基本上所有的iOS开发都用过,Json <---> Model。
主要的机制是采用了Runtime的反射机制,有兴趣学习Runtime的同学可以看看。
整理思路
如果你想要写一个 Json转Model的第三方,该怎么入手。
已知:
- 包含key value 的 dictionary
- model类
那么也就是,我们需要找出model里面的所有的property,然后把dictionary里面的value赋值给对应的property。
伪代码
+ (id)JsonToModel:(NSDictionary *)dictionary {
TestModel *model = [[TestModel alloc] init];
for(propertyName in TestModel){
[model setValue:dictionary[propertyName] forKey: propertyName];
}
}
说干就干
方法入口
调用:入口方法 mj_objectWithKeyValues最终会调用mj_setKeyValues
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
return [self mj_objectWithKeyValues:keyValues context:nil];
}
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
- (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];
Class clazz = [self class];
//通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 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;
}
...
...
安全判断代码
...
...
// 3.赋值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
}
}];
return self;
}
上面代码做了一定程度的简化,当然其实可以看的出来和前面的伪代码有点类似了。主要的逻辑就是:
- 获取Dictionary
- 从类里面拿到所有的propertyName
- 找到dictionary里面的value赋值给model
反射获取类中的属性
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration
{
// 获得成员变量
NSArray *cachedProperties = [self properties];
// 遍历成员变量
BOOL stop = NO;
for (MJProperty *property in cachedProperties) {
enumeration(property, &stop);
if (stop) break;
}
}
#pragma mark - 公共方法
+ (NSMutableArray *)properties
{
NSMutableArray *cachedProperties = [NSMutableArray array];
[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.释放内存
free(properties);
}];
return cachedProperties;
}
通过*objc_property_t properties = class_copyPropertyList(c, &outCount)这个方法获取所有的property,然后mj_enumerateClasses是遍历SuperClass,最后是把拿到的信息封装成一个MJProperty。
PS
其中代码进行了相应的简化,其中有一些Property的缓存,类型的安全判断都暂时忽略,只进行思路的理解。
纸上谈来终觉浅
写了一个Dome,大大简化了MJExtension,目的是为了浅尝Runtime的魅力。基本思路如最上面的伪代码。其中很多安全判断,省略了些许。
这里有一个Property Attribute,苹果文档比较详细参考:文档
Demo 地址:我是Demo
//Demo Class
#import "NSObject+TestExtension.h"
#import <objc/runtime.h>
static NSSet *foundationClasses_;
@implementation NSObject (TestExtension)
+ (instancetype)test_JsonToModelWithDictionary:(NSDictionary *)dict
{
Class clazz = [self class];
id model = [[clazz alloc] init];
unsigned int count;
objc_property_t *propertys = class_copyPropertyList(clazz, &count);
//1. 遍历Class中的Property
for (int i = 0; i < count; i++) {
NSString *propertyName = @(property_getName(propertys[i]));
NSString *propertyAttribute =@(property_getAttributes(propertys[i]));
// 2. 获取Value
id value = [dict objectForKey:propertyName];
if (!value || [value isKindOfClass:[NSNull class]]) {
continue;
}
// 3. 如果是其他类,递归调用
Class propertyClazz = [self getClassFromAttrs:propertyAttribute];
if (propertyClazz) {
value = [propertyClazz test_JsonToModelWithDictionary:value];
}
[model setValue:value forKey:propertyName];
}
return model;
}
+ (Class)getClassFromAttrs:(NSString *)attrs
{
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)];
}
if(code.length > 3 && [code hasPrefix:@"@\""])
{
// 去掉@"和",截取中间的类型名称
code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
Class clazz = NSClassFromString(code);
if (![self isClassFromFoundation:clazz]) {
return clazz;
}
}
return nil;
}
+ (BOOL)isClassFromFoundation:(Class)c
{
if (foundationClasses_ == nil) {
foundationClasses_ = [NSSet setWithObjects:
[NSURL class],
[NSDate class],
[NSValue class],
[NSData class],
[NSError class],
[NSArray class],
[NSDictionary class],
[NSString class],
[NSAttributedString class], nil];
}
if (c == [NSObject class]) return YES;
__block BOOL result = NO;
[foundationClasses_ enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) {
if ([c isSubclassOfClass:foundationClass]) {
result = YES;
*stop = YES;
}
}];
return result;
}
@end