【iOS】—— JSONModel

JSONModel源码

      • 1. JSONModel介绍
      • 2. JSONModel的其他用法
        • 2.1 转换属性名称
        • 2.2 自定义错误
      • 3. 源码分析
        • 3.1 - (id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
        • 3.2 JSONModel持有的数据
        • 3.3 load
        • 3.4 JSONModel的init方法
        • 3.5 __inspectProperties方法
        • 3.6 JSONModelClassProperty
        • 3.7 __doesDictionary方法
        • 3.8 __importDictionary
        • 3.9 到底什么是JSONKeyMapper?

1. JSONModel介绍

JSONModel是一个在iOS应用程序中使用的开源库,主要功能是JSON数据映射到Objective-C对象上。使用JSONModel可以方便地处理服务器返回的JSON格式的数据,将其转化为Objective-C对象,使得数据访问变的更加简单。

JSONModel提供了一种间接的方式来定义数据模型,开发者只需要创建一个继承自JSONModel的类,并在其中定义属性,将可以将JSON数据映射到对象上。JSONModel之间的嵌套关系可以通过嵌套JSONModel子类来实现。

总之,JSONModel是一个强大的数据映射工具,可以帮助开发者处理JSON数据,提高iOS开发的效率。

2. JSONModel的其他用法

2.1 转换属性名称

JSON的传入,在定义JSONModel数据模型的时候已经准备好某个数据名称,由于某些因素导致传入的JSON数据名称不符,就使用转换属性名称方法。

如下字典的key从url传入的时候变成了imageURL,这会就可以使用这个方法避免数据传入错误

在这里插入图片描述

+ (JSONKeyMapper *)keyMapper {return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"url": @"ImgaeURL"}];
}

这样就可以避免因为JSON数据和Model里面定义的数据不同而发生数据NULL的错误。

2.2 自定义错误

JSONModel除了一些框架里自己处理的错误,框架的作者允许自己定义属于自己的框架。方便对JSON数据传入的时候,是否进行转换的决定。

 - (BOOL)validate:(NSError *__autoreleasing *)error {}

3. 源码分析

看源码之前先上别人的流程图,配合着流程图看即可

在这里插入图片描述

上面的流程图,主要是下面这个方法:

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

这个方法内部是通过许多方法嵌套来完成的。

3.1 - (id)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;
}

在iOS开发中isKindOfClass和isMemberOfClass的区别

isKindOfClass: 判断对象是否当前类以及其子类的实例,如果是返回YES,不是返回NO。

isMemberOfClass: 判断对象是否为当前类的实例,如果是返回YES,不是返回NO。

总结上面6个方法:

  • 前4个方法,都是对错误的发现和处理。
  • 第五个方法是核心方法,判断模型映射。
  • 第六个方式是自定义错误判断,如果符合自定义的错误,就是mapping成功也会报错。
3.2 JSONModel持有的数据
#pragma mark - associated objects names
static const char * kMapperObjectKey;
static const char * kClassPropertiesKey;
static const char * kClassRequiredPropertyNamesKey;
static const char * kIndexPropertyNameKey;

关联对象kClassPropertiesKey: 保存数据所有属性信息的NSDictionary。

关联对象kClassRequiredPropertyNamesKey: 用来保存所有属性名称NSSet。

关联对象kMapperObjectKey: 用来保存JSONKeyMapper。

JSONModelClassProperty: 封装的JSONModel的一个属性,它包含了对应属性的名字(name:sex),类型(type:NSString),是否是JSONModel支持的类型(isStandardJSONType:YES/NO),是否是可变对象(isMutable:YES/NO)等属性。

总结一下完成模型的映射:

在模型类对象被初始化的时候,遍历自身到所有父类,获取所有的属性,将属性保存到一个字典中。获取传入到字典的key,将这些key与属性匹配,如果匹配成功的话,则进行KVC赋值。

3.3 load

在 Objective-C 中,每个类都有一个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));}});
}

在JSONModel类中,load主要是进行初始化操作:

  • 初始化allowedJSONTypes属性:这个属性包含所有兼容的JSON类型的数组。
  • 初始化allowedPrimitiveType属性:这个属性包含所有兼容基本数据类型的数组。
  • 初始化valueTransformer属性:这个是一个 JSONValueTransformer 类的实例。
  • 初始化 JSONModelClass 属性,这个属性是一个指向当前类的指针。

他们会被所有JSONModel子类所继承和共享,在子类中初始化这些属性是不必要的,在父类中都已经实现。

每个类都有自己的load方法,在自己的load方法实现一些必要的属性的初始化。

load方法在运行时加载一个类时自动调用,不论类被实例化多少次,都只会调用一次。

当在OC程序启动时,运行时会自动加载每个类的信息,在加载一个类的时候,会先检查这个类是否实现load,如果实现了,就调用该方法。

注意:

  • 分类中的load方法不会覆盖原始类中的load方法,而会在原始类的load方法调用之后再去执行。
3.4 JSONModel的init方法

通过源码发现 load方法之后紧跟着init方法的实现。看看init实现了什么,而init方法里面调用了一个-(void)__setup__方法,这个方法大有研究

 -(id)init
{self = [super init];if (self) {//do initial class setup进行初始类设置[self __setup__];}return self;
}-(void)__setup__
{//if first instance of this model, generate the property list//如果是此模型的第一个实例,请生成属性列表(只有第一次实例化的时候,才执行)if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {//该__inspectProperties方法是该第三方框架的核心方法之一:它的任务是保存了所有需要赋值的属性。用作在将来与传进来的字典进行映射[self __inspectProperties];}//if there's a custom key mapper, store it in the associated object//如果有自定义键映射器,请将其存储在关联的对象中(如果存在自定义的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这是原子的);}
}

__setup__方法中:

  1. if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey))的作用是检查当前类是否被解析过,如果没有解析过,就调用__inspectProperties方法解析类的属性信息并缓存。
  2. objc_getAssociatedObject 是一个用于获取关联对象的函数self.class是第一个参数,表示获取当前类的关联对象,如果关联对象不存在,就说明没有进行解析调用__inspectProperties方法进行解析。

关联对象的两个函数objc_getAssociatedObjectobjc_setAssociatedObject等函数用于关联对象的机制是 Objective-C 运行时提供的一种方法,**它可以在运行时为一个对象关联一些额外的数据,而不需要修改这个对象的定义。**这种机制在某些情况下非常方便,比如在 JSONModel 中就用它来缓存类的属性信息。

3.5 __inspectProperties方法

__inspectProperties方法该框架的核心方法:他的任务是保存所有需要赋值的属性,用做后来与传进来的字典进行映射。

__inspectProperties方法用于检查模型类的属性,并将其转化为一个NSDictionary对象。该方法检查模型类的属性,并将其转化为NSDictionary对象。该方法遍历模型类的属性,解析模型类的相关信息,存储在NSDictionary中。

该方法在运行时,为每一个属性创建JSONModelProperty对象,然后就这些对象保存到NSMultableDictionary中,以属性名为键,JSONModelProperty对象为值。最后NSMultableDictionary对象将会通过关联对象与模型类连接起来,方便访问。

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

注意:

  • JSONModelClassProperty类封装了JSONModel的每一个属性。这个类有两个重要的属性:一个是name,它是属性的名称(例如gender)。另一个是type,它是属性的类型(例如NSString)。
  • 作者将属性分为了如下几个类型:
    (1) 对象(不含有协议)。
    (2) 对象(含有协议,属于模型嵌套)。
    (3) 基本数据类型。
    (4) 结构体。
3.6 JSONModelClassProperty

JSONModelClassProperty是上述过程比较重要的一环,JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象,然后扫描property判断是否能够完成映射。

JSONModelClassProperty是JSONModel中用于描述一个属性的类。它包含了一个属性的各种信息,比如属性名、类型、元素类型(如果是数组类型的话)、转换器等。这个类成功的帮助JSONModel完成了许多冗杂的任务

JSONModelClassProperty定义了下列属性:

  • name:属性名
  • type:属性类型,用字符串表示,可以是基本类型,也可以是自定义类型。
  • isStandardJSONType:是否是标准的JSON类型。标准的JSON类型指的是:NSString、NSNumber、NSDecimalNumber、NSArray、NSDictionary和NSNull。
  • isMutable:属性是否可变。
  • isOptional:属性是否可选。
  • isArray:属性是否是数组类型。
  • elementType:如果属性是数组类型,这个属性指定了数组元素的类型。
  • customGetter:自定义的getter方法。
  • customSetter:自定义的setter方法。
  • getterType:getter方法返回值的类型。
  • setterType:setter方法参数的类型。
  • valueTransformer:值转换器。
3.7 __doesDictionary方法

__doesDictionary方法是JSONModel中的一个私有方法用于判断一个对象是否可以被转化为NSDictionary类型。如果可以,该方法会返回YES,否则返回NO

具体实现:通过OC的runtime机制获取对象的类的信息,判断是否实现了NSDictionary的allKey方法,如果实现了就说明对象可以被转化为NSDictionary类型,返回YES,否则返回NO。

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

model类里面的属性集合是不能大于传入字典中的key的集合**(我们所写的接受数据的属性的数量需要小于等于传入的字典的key集合)**

就会到了最后的核心方法,因为方法6的自定义错误是选择性实现的,若是通过这个方法的判断那么我们就即将完成模型和字典的映射了。

3.8 __importDictionary

同样也是返回BOOL类型的方法,很重要的一步。

__importDictionary方法内部是将NSDictionary对象转化为JSONModel对象的方法。

JSONModel需要将NSDictionary对象转化为JSONModel对象,需要调用__importDictionary方法。该方法首先通过classProperty获取JSONModel的属性列表,遍历每一个属性,根据属性名称,在字典中查找对应的值,并将值设置到JSONModel对象的对应属性中。

//作者在最后给属性赋值的时候使用的是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;
}

在设置属性值时,__importDictionary方法还会进行一些类型转换和校验

  • 如果属性是JSONModel类型,则将字典转换为该类型的JSONModel对象,并设置到当前属性中。
    如果属性是基本数据类型(如int、float等),则将字典中的数值转换为相应的数据类型,并设置到当前属性中。
  • 如果属性是NSDate类型,则将字典中的时间戳转换为NSDate对象,并设置到当前属性中。
    总之,__importDictionary方法的主要作用是将NSDictionary对象转换为JSONModel对象,并进行一些类型转换和校验。
3.9 到底什么是JSONKeyMapper?

在使用 JSONModel 时,JSON 数据中的属性名通常与我们所使用的类的属性名并不完全相同。因此,JSONModel 提供了一个叫做 JSONKeyMapper 的工具类,用于在 JSON 数据中查找与类属性名相对应的属性名,以便进行正确的映射。

JSONKeyMapper 是一个可定制的映射器,它提供了两种映射方式:

  • 下划线式(UnderscoreCase)映射:将下划线形式的 JSON 数据中的属性名转换成类属性名(如:foo_bar -> fooBar)。
  • 驼峰式(CamelCase)映射:将驼峰形式的 JSON 数据中的属性名转换成类属性名(如:fooBar -> foo_bar)。
 JSONKeyMapper *mapper = [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"propertyOne": @"property_one",@"propertyTwo": @"property_two"
}];
MyModel *model = [[MyModel alloc] initWithDictionary:jsonDict error:nil];

其实也就是为了方便我们前后端的数据类型发生改变的时候,添加一些自定义的属性映射方式避免不必要的错误。
除了开始说的重写+ (JSONKeyMapper *)keyMapper { return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"url": @"ImgaeURL"}]; }
也可以可以通过 JSONModel 中的 - (id)initWithDictionary:(NSDictionary *)dict error:(NSError **)err方法来传递一个自定义的 JSONKeyMapper实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404457.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8月19日笔记

http隧道搭建(续) ABPTTS安装使用 一款基于 SSL 加密的 HTTP 端口转发工具&#xff0c;全程通信数据加密&#xff0c;比 reGerog 都要稳定。使用 python2 编写&#xff0c;但是该工具只支持 aspx 和 jsp 脚本的网站。 下载地址&#xff1a;https://github.com/nccgroup/ABPTT…

测试报告---自动化测试

一、测试用例 上文铺垫了基础知识。 https://blog.csdn.net/m0_74876421/article/details/141307905https://blog.csdn.net/m0_74876421/article/details/141307905 二、性能测试 1.准备功能&#xff1a; 浏览器驱动以及selenim包 引入依赖&#xff1a;在pom.xml文件中添加…

selenium底层原理详解

目录 1、selenium版本的演变 1.1、Selenium 1.x&#xff08;Selenium RC时代&#xff09; 1.2、Selenium 2.x&#xff08;WebDriver整合时代&#xff09; 1.3、Selenium 3.x 2、selenium原理说明 3、源码说明 3.1、启动webdriver服务建立连接 3.2、发送操作 1、seleni…

【性能优化】修复一个谷歌官方承认的内存泄漏问题

前言 通过下面这段代码&#xff0c;配合控制台可以直观看到谷歌官方承认的一个内存泄漏问题&#xff0c;https://issues.chromium.org/issues/41403456。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta …

前端css动画transform多个属性值写法

X轴平移400px transform: translateX(400px); X轴平移400px并缩小0.5倍 transform: translateX(400px) scale(0.5); X轴平移400px并旋转45度 transform: translateX(400px) rotate(45d…

备考2024年美国数学竞赛AMC10:吃透1250道真题和知识点(持续)

有什么含金量比较高的初中生数学竞赛吗&#xff1f;美国数学竞赛AMC10是个不错的选择。那么&#xff0c;如何备考AMC10美国数学竞赛呢&#xff1f;做真题&#xff0c;吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。 通过做真题&#xff0c;可以帮助孩子找到真实竞赛…

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(二)---ROS2与UE5进行图像数据传输

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车&#xff0c;并使用通过跨平台的方式进行ROS2和UE5仿真的通讯&#xff0c;达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础&#xff0c;Nav2相关的学习教程可以参考本人的其他博…

系规学习第13天

1、规划设计的主要目的不包括() A、设计满足业务需求的IT服务 B、设计SLA、测量方法和指标。 C、设计服务过程及其控制方 D、设计实施规划所需要的进度管理过程 [答案] D [解析]本题考察的是规划设计的目的&#xff0c;建议掌握。 (1)设计满足业务需求的IT服务。 (2)设…

Axios请求使用params参数导致后端获取数据嵌套

问题重述&#xff1a; 首先看前端的axios请求这里我使用params参数将data数据传给后端 let data JSON.stringify(this.posts);axios.post("/blog_war_exploded/insertPost", {params: {data: data}}).then((res) > {if (res.data "success") {alert(…

大杂烩!注意力机制+时空特征融合!组合模型集成学习预测!CNN-LSTM-Attention-Adaboost多变量负荷预测

大杂烩&#xff01;注意力机制时空特征融合&#xff01;组合模型集成学习预测&#xff01;CNN-LSTM-Attention-Adaboost多变量负荷预测 目录 大杂烩&#xff01;注意力机制时空特征融合&#xff01;组合模型集成学习预测&#xff01;CNN-LSTM-Attention-Adaboost多变量负荷预测…

银河麒麟V10如何安装本地deb软件包?(以安装wps为例)

银河麒麟V10如何安装本地deb软件包&#xff1f;&#xff08;以安装wps为例&#xff09; 一、准备二、安装三、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在银河麒麟V10中安装本地.deb软件包&#xff0c;虽然apt主要用于管理仓库中…

LeetCode:3148. 矩阵中的最大得分(DP Java)

目录 3148. 矩阵中的最大得分 题目描述&#xff1a; 实现代码与解析&#xff1a; DP 原理思路&#xff1a; 3148. 矩阵中的最大得分 题目描述&#xff1a; 给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧…

删除微博博文js脚本实现

我当前的时间&#xff1a;2024.8.18 脚本可以直接使用&#xff0c;随着时间推移&#xff0c;微博页面元素可能会有变动。 思路&#xff1a;javascript 模拟手动点击&#xff0c;下滑&#xff0c;并且删除博文 首先登录微博&#xff0c;进入自己的博文界面如下&#xff1a; 进…

Git使用方法(三)---简洁版上传git代码

1 默认已经装了sshWindows下安装SSH详细介绍-CSDN博客 2 配置链接github的SSH秘钥 1 我的.ssh路径 2 进入路径cd .ssh 文件 3 生成密钥对 ssh-keygen -t rsa -b 4096 (-t 秘钥类型 -b 生成大小) 输入完会出现 Enter file in which to save the key (/c/Users/Administrator/…

使用DOM破坏启动xss

目录 实验环境&#xff1a; 分析&#xff1a; 找破坏点&#xff1a; 查看源码找函数&#xff1a; 找到了三个方法&#xff0c;loadComments、escapeHTM 、displayComments loadComments escapeHTM displayComments&#xff1a; GOGOGO 实验环境&#xff1a; Lab: Exp…

MySQL库表的基本操作

目录 1.库的操作1.1 创建数据库1.2字符集和校验规则①查看系统默认字符集以及校验规则②查看数据库支持的字符集③查看数据库支持的字符集校验规则④校验规则对数据库的影响 1.3操纵数据库①查看数据库②显示创建的数据库的语句③修改数据库④数据库删除⑤备份和恢复⑥还原注意…

C库函数signal()信号处理

signal()是ANSI C信号处理函数&#xff0c;原型如下&#xff1a; #include <signal.h>typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signal()将信号signum的处置设置为handler&#xff0c;该handler为SIG_IGN&#xff…

物联网(IoT)详解

物联网&#xff08;IoT&#xff09;详解 1. IoT定义简介2. IoT工作原理3. IoT关键技术4. 物联网与互联网区别5. IoT使用场景6. 开源物联网平台7. 参考资料 1. IoT定义简介 首先第一个问题&#xff0c;什么是物联网&#xff08;IoT&#xff09;? 物联网&#xff08;英文&#…

LabVIEW光纤水听器闭环系统

开发了一种利用LabVIEW软件开发的干涉型光纤水听器闭环工作点控制系统。该系统通过调节光源频率和非平衡干涉仪的光程差&#xff0c;实现了工作点的精确控制&#xff0c;从而提高系统的稳定性和检测精度&#xff0c;避免了使用压电陶瓷&#xff0c;使操作更加简便。 项目背景 …

thinkphp5实现弹出框(下拉框选项动态赋值)

效果图 原理 先执行接口获取动态数据&#xff0c;然后在 layer.open的success回调函数中动态添加html代码片段&#xff0c;通过如下方法&#xff0c;将动态生成的代码插入指定的div中&#xff0c;实现动态赋值的效果。 // 动态获取的数据 var data ......;// 弹出框配置 lay…