JSONModel源码解析
文章目录
前言
笔者学习一下有关于JSONModel的源码的内容,来了解它底层的一个实现过程
源码核心 initWithDictionary:error:
这里我们从JSONModel这个第三方库的一个核心开始介绍:
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
//方法1. 参数为nil
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//方法2. 参数不是nil,但也不是字典
if (![dict isKindOfClass:[NSDictionary class]]) {
if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
return nil;
}
// 前两个方法在进行一个入参校验
//方法3. 初始化
self = [self init];
if (!self) {
//初始化失败
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
//方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
//方法5. 核心方法:字典的key与模型的属性的映射
if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
return nil;
}
//方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
if (![self validate:err]) {
return nil;
}
//方法7. 终于通过了!成功返回model
return self;
}
- 前四个步骤都是对于错误的一个处理
- 方法5,这里真正的mapping
- 方法6是自定义给用户自己定义错误的方法,如果复合了用户自己定义的错误
- 方法7成功返回模型对象
这里我们先了解一下JSONModel所持有的一些数据:
- 关联对象kClassPropertiesKey:(用来保存所有属性信息的NSDictionary)
{
age = "@property primitive age (Setters = [])";
name = "@property NSString* name (Standard JSON type, Setters = [])";
gender = "@property NSString* gender (Standard JSON type, Setters = [])";
}
- 关联对象kClassRequiredPropertyNamesKey:(用来保存所有属性的名称NSSet)
{(
name,
age,
gender
)}
- 关联对象
kMapperObjectKey
,用来保存JSONKeyMapper
:自定义的mapper,具体的使用方法在上面的例子中可以看到 JSONModelClassProperty
分支JSONModel的一个属性,它包含了对应属性的名字:(name:gender).类型(type:NSString),是否是JSONModel支持的一个类型
首先,在这个模型类的对象被初始化的时候,遍历自身到所有的父类(直到JSONModel为止),获取所有的属性,并将其保存在一个字典里。获取传入字典的所有key,将这些key与保存的所有属性进行匹配。如果匹配成功,则进行kvc赋值。
load
方法里,定义了该框架支持的类型:
+(void)load {
static dispatch_once_t once;
dispatch_once(&once, ^{
@autoreleasepool {
//兼容的对象属性
allowedJSONTypes = @[
[NSString class], [NSNumber class], [NSDecimalNumber class], [NSArray class], [NSDictionary class], [NSNull class], //immutable JSON classes
[NSMutableString class], [NSMutableArray class], [NSMutableDictionary class] //mutable JSON classes
];
//兼容的基本类型属性
allowedPrimitiveTypes = @[
@"BOOL", @"float", @"int", @"long", @"double", @"short",
//and some famous aliases
@"NSInteger", @"NSUInteger",
@"Block"
];
//转换器
valueTransformer = [[JSONValueTransformer alloc] init];
//自己的类型
JSONModelClass = NSClassFromString(NSStringFromClass(self));
}
});
}
这里我们看一下方法3的init方法,作者都做了什么:
-(id)init
{
self = [super init];
if (self) {
[self __setup__];
}
return self;
}
-(void)__setup__
{
//只有第一次实例化时,才执行
if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
[self __inspectProperties];
}
//如果存在自定义的mapper,则将它保存在关联对象里面,key是kMapperObjectKey
id mapper = [[self class] keyMapper];
if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
objc_setAssociatedObject(
self.class,
&kMapperObjectKey,
mapper,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
}
这里我们看一下上面第一次实例化的时候所执行的核心函数:
__inspectProperties
-(void)__inspectProperties
{
// 最终保存所有属性的字典,形式为:
// {
// age = "@property primitive age (Setters = [])";
// friends = "@property NSArray* friends (Standard JSON type, Setters = [])";
// gender = "@property NSString* gender (Standard JSON type, Setters = [])";
// name = "@property NSString* name (Standard JSON type, Setters = [])";
// }
NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
//获取当前的类名
Class class = [self class];
NSScanner* scanner = nil;
NSString* propertyType = nil;
// 循环条件:当class 是 JSONModel自己的时候终止
while (class != [JSONModel class]) {
//属性的个数
unsigned int propertyCount;
//获得属性列表(所有@property声明的属性)
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
//遍历所有的属性
for (unsigned int i = 0; i < propertyCount; i++) {
//获得属性名称
objc_property_t property = properties[i];//获得当前的属性
const char *propertyName = property_getName(property);//name(C字符串)
//JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
p.name = @(propertyName);//propertyName:属性名称,例如:name,age,gender
//获得属性类型
const char *attrs = property_getAttributes(property);
NSString* propertyAttributes = @(attrs);
// T@\"NSString\",C,N,V_name
// Tq,N,V_age
// T@\"NSString\",C,N,V_gender
// T@"NSArray",&,N,V_friends
NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];
//说明是只读属性,不做任何操作
if ([attributeItems containsObject:@"R"]) {
continue; //to next property
}
//检查出是布尔值
if ([propertyAttributes hasPrefix:@"Tc,"]) {
p.structName = @"BOOL";//使其变为结构体
}
//实例化一个scanner
scanner = [NSScanner scannerWithString: propertyAttributes];
[scanner scanUpToString:@"T" intoString: nil];
[scanner scanString:@"T" intoString:nil];
if ([scanner scanString:@"@\"" intoString: &propertyType]) {
//属性是一个对象
[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]
intoString:&propertyType];//propertyType -> NSString
p.type = NSClassFromString(propertyType);// p.type = @"NSString"
p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound); //判断是否是可变的对象
p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是该框架兼容的类型
//存在协议(数组,也就是嵌套模型)
while ([scanner scanString:@"<" intoString:NULL]) {
NSString* protocolName = nil;
[scanner scanUpToString:@">" intoString: &protocolName];
if ([protocolName isEqualToString:@"Optional"]) {
p.isOptional = YES;
} else if([protocolName isEqualToString:@"Index"]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
p.isIndex = YES;
#pragma GCC diagnostic pop
objc_setAssociatedObject(
self.class,
&kIndexPropertyNameKey,
p.name,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
} else if([protocolName isEqualToString:@"Ignore"]) {
p = nil;
} else {
p.protocol = protocolName;
}
//到最接近的>为止
[scanner scanString:@">" intoString:NULL];
}
}
else if ([scanner scanString:@"{" intoString: &propertyType])
//属性是结构体
[scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
intoString:&propertyType];
p.isStandardJSONType = NO;
p.structName = propertyType;
}
else {
//属性是基本类型:Tq,N,V_age
[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
intoString:&propertyType];
//propertyType:q
propertyType = valueTransformer.primitivesNames[propertyType];
//propertyType:long
//基本类型数组
if (![allowedPrimitiveTypes containsObject:propertyType]) {
//类型不支持
@throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"
reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]
userInfo:nil];
}
}
NSString *nsPropertyName = @(propertyName);
//可选的
if([[self class] propertyIsOptional:nsPropertyName]){
p.isOptional = YES;
}
//可忽略的
if([[self class] propertyIsIgnored:nsPropertyName]){
p = nil;
}
//集合类
Class customClass = [[self class] classForCollectionProperty:nsPropertyName];
if (customClass) {
p.protocol = NSStringFromClass(customClass);
}
//忽略block
if ([propertyType isEqualToString:@"Block"]) {
p = nil;
}
//如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)
if (p && ![propertyIndex objectForKey:p.name]) {
[propertyIndex setValue:p forKey:p.name];
}
//setter 和 getter
if (p)
{ //name ->Name
NSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];
// getter
SEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);
if ([self respondsToSelector:getter])
p.customGetter = getter;
// setters
p.customSetters = [NSMutableDictionary new];
SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);
if ([self respondsToSelector:genericSetter])
p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];
for (Class type in allowedJSONTypes)
{
NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);
if (p.customSetters[class])
continue;
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);
if ([self respondsToSelector:setter])
p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];
}
}
}
free(properties);
//再指向自己的父类,知道等于JSONModel才停止
class = [class superclass];
}
//最后保存所有当前类,除JSONModel这个类外的的所有的父类的属性
objc_setAssociatedObject(
self.class,
&kClassPropertiesKey,
[propertyIndex copy], // 最后生成的属性字典
OBJC_ASSOCIATION_RETAIN
);
}
这里需要注意以下几个点:
- 作者
while
函数,获取当前类和当前类的除了JSONModel
的所有父类的属性保存到一个字典中,在将来用于和传入的字典进行一个映射- 这里用
JSONModelClassProperty
类封装了JSONModel的每一个属性,这个类有两个重要的属性一个是name
.另一个是type
,这个是属性的类型- 作者将属性分成类下面几个类型:
- 对象(不含有协议)
- 对象(含有协议,属于模型嵌套)
- 基本数据类型
- 结构体
在最后将这个属性字典通过关联对象绑定到我们的类上面.
这时候我们再看上面的initWithDictionary
的第四个步骤:
//方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
这里我们在进入__doesDictionary
这个方法里面看一下它做了什么事情?
__doesDictionary
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
//check if all required properties are present
//获得字典里面的所有key
NSArray* incomingKeysArray = [dict allKeys];
//返回保存所有属性名称的数组(name, age, gender)
NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
//从array中拿到set
NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
//如果用户自定义了mapper,则进行转化
//transform the key names, if necessary
if (keyMapper || globalKeyMapper) {
NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];
NSString* transformedName = nil;
//loop over the required properties list
// 遍历所有需要被转化的属性列表
for (JSONModelClassProperty* property in [self __properties__]) {
transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
//check if exists and if so, add to incoming keys
id value;
@try {
value = [dict valueForKeyPath:transformedName];
}
@catch (NSException *exception) {
value = dict[transformedName];
}
if (value) {
[transformedIncomingKeys addObject: property.name];
}
}
//overwrite the raw incoming list with the mapped key names
incomingKeys = transformedIncomingKeys;
}
// 查看当前model的属性集合是否大于传入的属性集合.如果是则返回错误
//也就是说模型类里的属性使不能多与传入字典的key的
//check for missing input keys
if (![requiredProperties isSubsetOfSet:incomingKeys]) {
//get a list of the missing properties
[requiredProperties minusSet:incomingKeys];
//not all required properties are in - invalid input
JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);
if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
return NO;
}
// 不需要了释放掉
//not needed anymore
incomingKeys= nil;
requiredProperties= nil;
return YES;
}
这里需要注意的:
- model类里面定义的属性集合是不能大于传入的字典的key集合
- 如果存在了用户自定义的mapper,则需要按照用户的定义来进行转化
现在看上面的方法5,也就是当前真正从字典里面获取值并且赋值给当前模型对象的实现:
__importDictionary
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
//遍历保存的所有属性的字典
for (JSONModelClassProperty* property in [self __properties__]) {
//将属性的名称拿过来,作为key,用这个key来查找传进来的字典里对应的值
NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
//用来保存从字典里获取的值
id jsonValue;
@try {
jsonValue = [dict valueForKeyPath: jsonKeyPath];
}
@catch (NSException *exception) {
jsonValue = dict[jsonKeyPath];
}
//字典不存在对应的key
if (isNull(jsonValue)) {
//如果这个key是可以不存在的
if (property.isOptional || !validation) continue;
//如果这个key是必须有的,则返回错误
if (err) {
NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
//获取 取到的值的类型
Class jsonValueClass = [jsonValue class];
BOOL isValueOfAllowedType = NO;
//查看是否是本框架兼容的属性类型
for (Class allowedType in allowedJSONTypes) {
if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
isValueOfAllowedType = YES;
break;
}
}
//如果不兼容,则返回NO,mapping失败
if (isValueOfAllowedType==NO) {
//type not allowed
JMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));
if (err) {
NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
//如果是兼容的类型:
if (property) {
// 查看是否有自定义setter,并设置
if ([self __customSetValue:jsonValue forProperty:property]) {
continue;
};
// 基本类型
if (property.type == nil && property.structName==nil) {
//kvc赋值
if (jsonValue != [self valueForKey:property.name]) {
[self setValue:jsonValue forKey: property.name];
}
continue;
}
// 如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它
if (isNull(jsonValue)) {
if ([self valueForKey:property.name] != nil) {
[self setValue:nil forKey: property.name];
}
continue;
}
// 1. 属性本身是否是jsonmodel类型
if ([self __isJSONModelSubClass:property.type]) {
//通过自身的转模型方法,获取对应的值
JSONModelError* initErr = nil;
id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
if (!value) {
//如果该属性不是必须的,则略过
if (property.isOptional || !validation) continue;
//如果该属性是必须的,则返回错误
if((err != nil) && (initErr != nil))
{
*err = [initErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
//当前的属性值为空,则赋值
if (![value isEqual:[self valueForKey:property.name]]) {
[self setValue:value forKey: property.name];
}
continue;
} else {
// 如果不是jsonmodel的类型,则可能是一些普通的类型:NSArray,NSString。。。
// 是否是模型嵌套(带有协议)
if (property.protocol) {
//转化为数组,这个数组就是例子中的friends属性。
jsonValue = [self __transform:jsonValue forProperty:property error:err];
if (!jsonValue) {
if ((err != nil) && (*err == nil)) {
NSString* msg = [NSString stringWithFormat:@"Failed to transform value, but no error was set during transformation. (%@)", property];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
}
// 对象类型
if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {
//可变类型
if (property.isMutable) {
jsonValue = [jsonValue mutableCopy];
}
//赋值
if (![jsonValue isEqual:[self valueForKey:property.name]]) {
[self setValue:jsonValue forKey: property.name];
}
continue;
}
// 当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从NSSet到NSArray转换:- (NSSet *)NSSetFromNSArray:(NSArray *)array)
if (
(![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))
||
//the property is mutable
property.isMutable
||
//custom struct property
property.structName
) {
// searched around the web how to do this better
// but did not find any solution, maybe that's the best idea? (hardly)
Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];
//JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);
//build a method selector for the property and json object classes
NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",
(property.structName? property.structName : property.type), //target name
sourceClass]; //source name
SEL selector = NSSelectorFromString(selectorName);
//查看自定义的转换器是否存在
BOOL foundCustomTransformer = NO;
if ([valueTransformer respondsToSelector:selector]) {
foundCustomTransformer = YES;
} else {
//try for hidden custom transformer
selectorName = [NSString stringWithFormat:@"__%@",selectorName];
selector = NSSelectorFromString(selectorName);
if ([valueTransformer respondsToSelector:selector]) {
foundCustomTransformer = YES;
}
}
//如果存在自定义转换器,则进行转换
if (foundCustomTransformer) {
IMP imp = [valueTransformer methodForSelector:selector];
id (*func)(id, SEL, id) = (void *)imp;
jsonValue = func(valueTransformer, selector, jsonValue);
if (![jsonValue isEqual:[self valueForKey:property.name]])
[self setValue:jsonValue forKey:property.name];
} else {
//没有自定义转换器,返回错误
NSString* msg = [NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithTypeMismatch:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
return NO;
}
} else {
// 3.4) handle "all other" cases (if any)
if (![jsonValue isEqual:[self valueForKey:property.name]])
[self setValue:jsonValue forKey:property.name]; // 通过KVC赋值
}
}
}
}
return YES;
}
其实这里也就分成以下四个步骤:
- 首先遍历模型类中的所有属性
- 找到JSON字典中与模型类对应的key
- 获取jsonValue
- 进行一系列的检查操作,最后通过
KVC
来将JSON字典中的值赋值给了模型类
小结
JSONModel实现了将JSON数据转化成Model数据:
- 将得到的JSON数据自动与Model进行匹配
- 还提供类keyMapper将JSON建映射到正确的模型的属性上
- 还可以让我们可以自定义错误处理
方法中通过获取JSONModel类的属性列表,与传入的JSON数据自动匹配,同时还可以通过KeyMapper修改不相同的映射,如果模型类与JSON数据字段不匹配则会抛出错误(这里体现为Model中某些必须的属性没有在JSON数据中找到相应的映射),最后如果类型等都检查成功,则通过KVC将JSON数据中的value设置在Model类的对应的属性上
这里在总结一下JSONModel的几个优点:
- 通过Runtime进行一个动态解析Model的数据
- 然后在setup方法中通过
关联对象
缓存已经解析过的数据,减少类解析的流程 - 通过
KVC
赋值修改不一致的JSON子段和属性