当前位置: 首页 > news >正文

【iOS】JSONModel源码阅读笔记

文章目录

  • 前言
  • 一、JSONModel使用
  • 二、JSONModel其他方法
    • 转换属性名称
  • 三、源码分析
    • - (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err
      • [self init]
      • __setup__
      • __inspectProperties
    • - (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper
    • - (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
  • 总结


前言

JSONModel是一个很经典的源码库,先前基本把iOS的底层看的差不多了,暑假之前都会进行源码与算法学习

一、JSONModel使用

之前已经详细写过使用,有一些没写到的使用会在后面补充
【iOS】JSONModel的基本使用

二、JSONModel其他方法

转换属性名称

有时候我们的JSONModel的属性名称与传入的字典的键值不对应,我们就需要使用keyMapper来转换JSON键,将模型中的属性转换为KeyMapper中对应的键名dictionary中的key值为属性名,value为json键名

#import <JSONModel/JSONModel.h>@interface UserProfile : JSONModel@property (strong, nonatomic) NSString* userId;
@property (strong, nonatomic) NSString* emailAddress;
@property (strong, nonatomic) NSString* firstName;
@property (strong, nonatomic) NSString* lastName;@end

设置键映射
为了将JSON键映射到正确的模型属性,你需要在模型类中重写+ (JSONKeyMapper *)keyMapper方法,指定如何将模型中的属性转换为JSON中的键名

@implementation UserProfile// 使用 JSONKeyMapper 来定义 JSON 字段与模型属性之间的映射关系
+ (JSONKeyMapper *)keyMapper {return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"userId": @"user_id",@"emailAddress": @"email_address",@"firstName": @"profile_info.first_name",@"lastName": @"profile_info.last_name"}];
}
@end

三、源码分析

先看流程图
在这里插入图片描述

看一下源代码的目录,可以看到以下内容
在这里插入图片描述

在JSONModel中提供了四种初始化方法

-(instancetype)initWithString:(NSString*)string error:(JSONModelError**)err;
-(instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err;
-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
-(instancetype)initWithData:(NSData *)data error:(NSError **)error;

四种方法大同小异,我们从最经典的-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;讲起

- (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err

还是先查看源代码

-(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;
}

可以看到源代码分为了七步,我们来逐步讲解

  • 第一步:首先进行判错操作,查询参数是否为空,如果为空则直接返回nil
  • 第二步:查看参数dict是否为字典类型,如果不是则也返回nil
  • 第三步:初始化JSONModel模型,设置Model的属性集合
  • 第四步:查询是否存在KeyMapper,如果有则进行底层dict键名的变换,然后验证Model中的属性是否都在dict的键名匹配,如果有没有被匹配的则会报错
  • 第五步:核心方法,终于到了dict的键值与模型的Model的互相映射
  • 第六步:可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
  • 第七步:返回JSONModel

整个过程看起来并不难,但是涉及到的底层知识点比较多

重点主要在第三步第四步第五步,我们来看一下

但是在讲解之前,我们必须讲一下JSONModel中持有的一些关联对象的数据
在这里插入图片描述

  • 关联对象kClassPropertiesKey:(用来保存所有属性信息的NSDictionary)
  • 关联对象kClassRequiredPropertyNamesKey:(用来保存所有属性的名称NSSet)
  • 关联对象kMapperObjectKey:(用来保存JSONKeyMapper):自定义的mapper,具体的使用方法在上面的例子中可以看到。

[self init]

先来看一下第三步的源代码

self = [self init];if (!self) {//super init didn't succeedif (err) *err = [JSONModelError errorModelIsInvalid];return nil;}

[self init]的代码调用了init初始化函数,实现如下,我们主要关注的是其中[self setup]

- (id)init
{self = [super init];if (self) {//do initial class setup[self __setup__];}return self;
}

setup

来看一下setup的实现

- (void)__setup__
{//if first instance of this model, generate the property list// 如果是该模型的第一个实例,则生成属性列表if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {[self __inspectProperties];}//if there's a custom key mapper, store it in the associated objectid mapper = [[self class] keyMapper];if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {objc_setAssociatedObject(self.class,&kMapperObjectKey,mapper,OBJC_ASSOCIATION_RETAIN // This is atomic);}
}

我们来逐步分析
第一句代码(!objc_getAssociatedObject(self.class, &kClassPropertiesKey))查看当前类是否存在关联对象,如果是该模型的第一个实例,则生成属性列表,也就是调用[self __inspectProperties];检索属性

检索完之后如果存在keyMapperkeyMapper也与模型类进行关联

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];//http://blog.csdn.net/kmyhy/article/details/8258858           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);
}

这是一串十分长的代码,先来大概讲解一下这个方法:
这个方法用于检索JSONModel类中的属性,并将其转化为一个可用的 NSDictionary 对象。该方法会遍历模型类的属性,然后解析每个属性的相关信息(如属性名、数据类型、对应的 JSON 字段名等),并将其存储在 NSDictionary 对象中,也就是上文的propertyIndex

如果我们具体分析代码流程,就会发现:

  • 该方法会先使用运行时函数获取JSONModel的属性列表
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
  • 然后为每个属性创建一个JSONModelProperty 对象,也就是下文的p实例
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
  • p实例也是我们创建的dictionaryvalue
if (p && ![propertyIndex objectForKey:p.name]) {[propertyIndex setValue:p forKey:p.name];
}
  • JSONModelProperty 对象中包含了属性名、数据类型、对应的 JSON 字段名等信息,以下代码可见一斑
p.type = NSClassFromString(propertyType);
p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];
  • 最后所有的这些 JSONModelProperty 对象都会存储在一个NSMutableDictionary 对象——propertyIndex中,然后通过objc_setAssociatedObject与模型进行关联,这一步是因为先前没有设置关联,如果不是第一次实例化这个类就不会调用__inspectProperties方法
//最后保存所有当前类,JSONModel的所有的父类的属性
objc_setAssociatedObject(self.class,&kClassPropertiesKey,[propertyIndex copy],OBJC_ASSOCIATION_RETAIN
);
  • 同时需要注意当前类会不断沿superclass继承链向上检索直到父类为JSONModel
 while (class != [JSONModel class]) {............................................class = [class superclass];}

- (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper

在这一步,我们先不看源码,我们直译一下这个方法:
字典是否与拥有KeyMapper的模型匹配

那么我们就很容易理解这个方法的作用,就是检查字典与模型是否匹配,在上一个方法中我们将

//model类里面定义的属性集合是不能大于传入的字典里的key集合的。
//如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。
//(例如将gender转换为了sex)。
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{//check if all required properties are present//拿到字典里所有的keyNSArray* incomingKeysArray = [dict allKeys];NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;//从array拿到setNSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];//transform the key names, if necessary//如有必要,变换键名称//如果用户自定义了mapper,则进行转换if (keyMapper || globalKeyMapper) {NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];NSString* transformedName = nil;//loop over the required properties list//在所需属性列表上循环//遍历需要转换的属性列表for (JSONModelClassProperty* property in [self __properties__]) {//被转换成的属性名称(例如)TestModel(模型内) -> url(字典内)transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//check if exists and if so, add to incoming keys//检查是否存在,如果存在,则添加到传入密钥//(例如)拿到url以后,查看传入的字典里是否有url对应的值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;}//check for missing input keys//检查是否缺少输入键//查看当前的model的属性的集合是否大于传入的属性集合,如果是,则返回错误//也就是说模型类里的属性是不能多于传入字典里的key的,例如:if (![requiredProperties isSubsetOfSet:incomingKeys]) {//get a list of the missing properties//获取缺失属性的列表(获取多出来的属性)[requiredProperties minusSet:incomingKeys];//not all required properties are in - invalid input//并非所有必需的属性都在 in - 输入无效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;
}
  • 首先获取dict中所有的键名
    NSArray* incomingKeysArray = [dict allKeys];
  • 其次获取模型类中所有的属性名
NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;

这里跳进__requiredPropertyNames方法看一下:

-(NSMutableSet*)__requiredPropertyNames
{//fetch the associated property namesNSMutableSet* classRequiredPropertyNames = objc_getAssociatedObject(self.class, &kClassRequiredPropertyNamesKey);if (!classRequiredPropertyNames) {classRequiredPropertyNames = [NSMutableSet set];[[self __properties__] enumerateObjectsUsingBlock:^(JSONModelClassProperty* p, NSUInteger idx, BOOL *stop) {if (!p.isOptional) [classRequiredPropertyNames addObject:p.name];}];//persist the listobjc_setAssociatedObject(self.class,&kClassRequiredPropertyNamesKey,classRequiredPropertyNames,OBJC_ASSOCIATION_RETAIN // This is atomic);}return classRequiredPropertyNames;
}

我们先前说了,kClassRequiredPropertyNamesKey是一个用来保存所有属性的名称NSSet,因此调用这个方法可以让requiredProperties获取模型类中的所有属性名

  • 将dict中得到的key数组转换为set类型
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
  • 如果存在keyMapper或是globalKeyMapper,则将模型中的属性名转换为KeyMapper中对应的Value,也就是将JSONModel中的属性名转换为Json键名
transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;

具体如何转换可以跳进-(NSString*)__mapString:(NSString*)string withKeyMapper:(JSONKeyMapper*)keyMapper方法查看

  • 最后更新dict的键名集合,这样做是为了保证dict中的每个键名都有对应的有效值,而不是仅仅只有一个key键
incomingKeys = transformedIncomingKeys;
  • 最后查看模型类的属性列表是否为JSON键值集合的子集
    如果不是则会报错,意味着JSON中的数据不能完全覆盖我们声明的属性,说明我们有属性得不到赋值,因此会判断出错
    //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;}

- (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err

前面进行了许多验证的操作,终于到了将JSON数据导入模型中的操作了

//作者在最后给属性赋值的时候使用的是kvc的setValue:ForKey:的方法。
//作者判断了模型里的属性的类型是否是JSONModel的子类,可见作者的考虑是非常周全的。
//整个框架看下来,有很多的地方涉及到了错误判断,作者将将错误类型单独抽出一个类(JSONModelError),里面支持的错误类型很多,可以侧面反应作者思维之缜密。而且这个做法也可以在我们写自己的框架或者项目中使用。
//从字典里获取值并赋给当前模型对象
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{//loop over the incoming keys and set self's properties//遍历保存的所有属性的字典for (JSONModelClassProperty* property in [self __properties__]) {//convert key name to model keys, if a mapper is provided//将属性的名称(若有改动就拿改后的名称)拿过来,作为key,用这个key来查找传进来的字典里对应的值NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//JMLog(@"keyPath: %@", jsonKeyPath);//general check for data type compliance//用来保存从字典里获取的值id jsonValue;@try {jsonValue = [dict valueForKeyPath: jsonKeyPath];}@catch (NSException *exception) {jsonValue = dict[jsonKeyPath];}//check for Optional properties//检查可选属性//字典不存在对应的keyif (isNull(jsonValue)) {//skip this property, continue with next property//跳过此属性,继续下一个属性//如果这个key是可以不存在的if (property.isOptional || !validation) continue;//如果这个key是必须有的,则返回错误if (err) {//null value for required property//所需属性的值为nullNSString* 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;}//check if there's matching property in the model//检查模型中是否有匹配的属性//如果是兼容的类型:if (property) {// check for custom setter, than the model doesn't need to do any guessing// how to read the property's value from JSON//检查自定义setter,则模型不需要进行任何猜测(查看是否有自定义setter,并设置)//如何从JSON读取属性值if ([self __customSetValue:jsonValue forProperty:property]) {//skip to next JSON key//跳到下一个JSON键continue;};// 0) handle primitives//0)句柄原语//基本类型if (property.type == nil && property.structName==nil) {//generic setter//通用setter//kvc赋值if (jsonValue != [self valueForKey:property.name]) {[self setValue:jsonValue forKey: property.name];}//skip directly to the next key//直接跳到下一个键continue;}// 0.5) handle nils//如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它if (isNull(jsonValue)) {if ([self valueForKey:property.name] != nil) {[self setValue:nil forKey: property.name];}continue;}// 1) check if property is itself a JSONModel//检查属性本身是否是jsonmodel类型if ([self __isJSONModelSubClass:property.type]) {//initialize the property's model, store it//初始化属性的模型,并将其存储//通过自身的转模型方法,获取对应的值JSONModelError* initErr = nil;id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];if (!value) {//skip this property, continue with next property//跳过此属性,继续下一个属性(如果该属性不是必须的,则略过)if (property.isOptional || !validation) continue;// Propagate the error, including the property name as the key-path component//传播错误,包括将属性名称作为密钥路径组件(如果该属性是必须的,则返回错误)if((err != nil) && (initErr != nil)){*err = [initErr errorByPrependingKeyPathComponent:property.name];}return NO;}//当前的属性值与value不同时,则赋值if (![value isEqual:[self valueForKey:property.name]]) {[self setValue:value forKey: property.name];}//for clarity, does the same without continue//为清楚起见,不继续执行相同操作continue;} else {// 2) check if there's a protocol to the property//  ) might or not be the case there's a built in transform for it//2)检查是否有协议//)可能是,也可能不是,它有一个内置的转换if (property.protocol) {//JMLog(@"proto: %@", p.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;}}// 3.1) handle matching standard JSON types//3.1)句柄匹配标准JSON类型//对象类型if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {//mutable properties//可变类型的属性if (property.isMutable) {jsonValue = [jsonValue mutableCopy];}//set the property value//为属性赋值if (![jsonValue isEqual:[self valueForKey:property.name]]) {[self setValue:jsonValue forKey: property.name];}continue;}// 3.3) handle values to transform//3.3)处理要转换的值//当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从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//为属性和json对象类构建方法选择器NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",(property.structName? property.structName : property.type), //target name目标名sourceClass]; //source name源名称SEL selector = NSSelectorFromString(selectorName);//check for custom transformer//查看自定义的转换器是否存在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;}}//check if there's a transformer with that name//检查是否有同名变压器//如果存在自定义转换器,则进行转换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 {//如果没有自定义转换器,返回错误if (err) {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)//3.4)处理“所有其他”情况(如有)if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];}}}}return YES;
}
  • 首先遍历模型类中的所有属性
for (JSONModelClassProperty* property in [self __properties__])
  • 找到JSON字典中与模型类对应的key
        NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
  • 获取keyvalue,这里不用担心获取为空,因为在上一步验证dict是否与keymapper有匹配的方法中已经验证过了
id jsonValue;@try {jsonValue = [dict valueForKeyPath: jsonKeyPath];}@catch (NSException *exception) {jsonValue = dict[jsonKeyPath];}
  • 进行一系列检查操作,就不讲了,最后终于通过KVC将JSON字典中的值赋给了模型类
            if (property.type == nil && property.structName==nil) {//generic setter//通用setter//kvc赋值if (jsonValue != [self valueForKey:property.name]) {[self setValue:jsonValue forKey: property.name];}//skip directly to the next key//直接跳到下一个键continue;}

总结

JSONModel提供了一种方法,

  • 得到的JSON数据自动与Model进行匹配
  • 还提供了keyMapper将JSON键自动映射到模型的属性
  • 还让我们可以自定义错误处理

这里再讲一下JSONModel的实现流程:

方法中通过获取JSONModel类的属性列表,与传入的JSON数据自动匹配,同时还可以通过KeyMapper修改不相同的映射,如果模型类与JSON数据字段不匹配则会抛出错误(这里体现为Model中某些必须的属性没有在JSON数据中找到相应的映射),最后如果类型等都检查成功,则通过KVC将JSON数据中的value设置在Model类的对应的属性上

if (jsonValue != [self valueForKey:property.name]) {[self setValue:jsonValue forKey: property.name];

这里再总结一下JSONModel的几个优点

  • 首先就是Runtime中动态解析Model数据类型,可以实现自动匹配
  • 然后在setup方法中还通过AssociatedObject实现了已经解析过了Model的属性列表等必须信息的缓存,避免了相同模型的重复解析
  • 还有就是KVC附值
  • 使用KeyMapper修改不一致的JSON字段与属性

这里还有必要再讲一下KeyMapper,你可以通过重写模型类中的 +keyMapper 方法来自定义键映射。这个方法需要返回一个 JSONKeyMapper 对象,该对象通过初始化方法接收一个字典来定义映射关系。

@interface User : JSONModel
@property (strong, nonatomic) NSString *userId;
@property (strong, nonatomic) NSString *email;
@property (strong, nonatomic) NSString *userDescription;
@end@implementation User+ (JSONKeyMapper *)keyMapper {return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"userId": @"id",@"email": @"email_address",@"userDescription": @"description"}];
}@end

参考博客:
JSONModel源码解析
【iOS-JSONModel源码】
JSONModel源代码解析

相关文章:

  • 【线性代数】第七章-二次型
  • Python闯LeetCode--第3题:无重复字符的最长子串
  • Node.js和npm的安装及配置
  • Centos系统yum安装mysql数据库
  • 将自己md文件发布到自己的博客园实现文件的持久化存储
  • FreeRTOS简单内核实现6 优先级
  • 小白跟做江科大32单片机之定时器输出比较
  • day38-39| 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯 62.不同路径 343. 整数拆分 96.不同的二叉搜索树
  • 数据结构--力扣104,110 二叉树相关(C
  • springboot+shiro+jwt 兼容session和token
  • mac m芯片安装win11遇坑
  • MySQL 之 JSON 支持(三)—— JSON 函数
  • Centos7.9部署单节点K8S环境
  • 计算机网络之网络层知识总结
  • 前端HTML相关知识
  • 「面试题」如何实现一个圣杯布局?
  • Android 控件背景颜色处理
  • AWS实战 - 利用IAM对S3做访问控制
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • IDEA 插件开发入门教程
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Octave 入门
  • opencv python Meanshift 和 Camshift
  • Python 反序列化安全问题(二)
  • SQLServer之创建数据库快照
  • Yii源码解读-服务定位器(Service Locator)
  • 阿里云Kubernetes容器服务上体验Knative
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 微信小程序设置上一页数据
  • nb
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • Linux权限管理(week1_day5)--技术流ken
  • 如何用纯 CSS 创作一个货车 loader
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (06)金属布线——为半导体注入生命的连接
  • (09)Hive——CTE 公共表达式
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (编译到47%失败)to be deleted
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .CSS-hover 的解释
  • .Family_物联网
  • .Net CF下精确的计时器
  • .Net Core和.Net Standard直观理解
  • .NET delegate 委托 、 Event 事件
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .NET单元测试
  • @31省区市高考时间表来了,祝考试成功
  • @RequestMapping-占位符映射
  • @RequestParam详解
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429