【iOS】JSONModel源码学习
JSONModel源码解析
文章目录
- JSONModel源码解析
- 前言
- 源码核心 `initWithDictionary:error:`
- __inspectProperties
- __doesDictionary
- __importDictionary
- 小结
前言
笔者学习一下有关于JSONModel的源码的内容,来了解它底层的一个实现过程
源码核心 initWithDictionary:error:
这里我们从JSONModel这个第三方库的一个核心开始介绍:
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{//方法1. 参数为nilif (!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. 终于通过了!成功返回modelreturn 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是kMapperObjectKeyid 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";//使其变为结构体} //实例化一个scannerscanner = [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 popobjc_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:qpropertyType = 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);}//忽略blockif ([propertyType isEqualToString:@"Block"]) {p = nil;}//如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)if (p && ![propertyIndex objectForKey:p.name]) {[propertyIndex setValue:p forKey:p.name];}//setter 和 getterif (p){ //name ->NameNSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];// getterSEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);if ([self respondsToSelector:getter])p.customGetter = getter;// settersp.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//获得字典里面的所有keyNSArray* incomingKeysArray = [dict allKeys];//返回保存所有属性名称的数组(name, age, gender)NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;//从array中拿到setNSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];//如果用户自定义了mapper,则进行转化//transform the key names, if necessaryif (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 keysid 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 namesincomingKeys = transformedIncomingKeys;}// 查看当前model的属性集合是否大于传入的属性集合.如果是则返回错误//也就是说模型类里的属性使不能多与传入字典的key的//check for missing input keysif (![requiredProperties isSubsetOfSet:incomingKeys]) {//get a list of the missing properties[requiredProperties minusSet:incomingKeys];//not all required properties are in - invalid inputJMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];return NO;}// 不需要了释放掉//not needed anymoreincomingKeys= 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];}//字典不存在对应的keyif (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 allowedJMLog(@"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 mutableproperty.isMutable||//custom struct propertyproperty.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 classesNSString* selectorName = [NSString stringWithFormat:@"%@From%@:",(property.structName? property.structName : property.type), //target namesourceClass]; //source nameSEL selector = NSSelectorFromString(selectorName);//查看自定义的转换器是否存在BOOL foundCustomTransformer = NO;if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES; } else {//try for hidden custom transformerselectorName = [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子段和属性