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

【iOS】—— JSONModel源码学习

JSONModel

文章目录

  • JSONModel
    • 关于JSONModel的用法
    • initWithDictionary等方法
    • load方法实现
      • load方法调用时机
    • init方法
      • __setup__方法
      • __inspectProperties:方法
    • __doesDictionary方法
    • __importDictionary方法

关于JSONModel的用法

可以参考之前写的博客:【iOS】—— JSONModel使用以及Manager封装网络请求

initWithDictionary等方法

整个过程用到的变量:

#pragma mark - associated objects names     //关联对象名称
static const char * kMapperObjectKey;
//关联对象kMapperObjectKey,保存自定义的mapperstatic const char * kClassPropertiesKey;
//关联对象kClassPropertiesKey,用来保存所有属性信息的NSDictionarystatic const char * kClassRequiredPropertyNamesKey;
//关联对象kClassRequiredPropertyNamesKey,用来保存所有属性的名称NSSetstatic const char * kIndexPropertyNameKey;
//关联对象kIndexPropertyNameKey,储存符合 Index 协议的属性名#pragma mark - class static variables     //类静态变量
static NSArray* allowedJSONTypes = nil;
static NSArray* allowedPrimitiveTypes = nil;
static JSONValueTransformer* valueTransformer = nil;
static Class JSONModelClass = NULL; 

JSONModel中含有许多init方法:

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

我们仔细观察即可发现,前面的方法在实现过程中,做了简单的处理,最终还是调用了initWithDictionary:

-(instancetype)initWithData:(NSData *)data error:(NSError *__autoreleasing *)err
{//check for nil inputif (!data) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//read the jsonJSONModelError* initError = nil;id obj = [NSJSONSerialization JSONObjectWithData:dataoptions:kNilOptionserror:&initError];if (initError) {if (err) *err = [JSONModelError errorBadJSON];return nil;}//init with dictionaryid objModel = [self initWithDictionary:obj error:&initError];if (initError && err) *err = initError;return objModel;
}-(id)initWithString:(NSString*)string error:(JSONModelError**)err
{JSONModelError* initError = nil;id objModel = [self initWithString:string usingEncoding:NSUTF8StringEncoding error:&initError];if (initError && err) *err = initError;return objModel;
}-(id)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err
{//check for nil inputif (!string) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}JSONModelError* initError = nil;id objModel = [self initWithData:[string dataUsingEncoding:encoding] error:&initError];if (initError && err) *err = initError;return objModel;}

那我们来看看initWithDictionary方法的实现:

//这个方法里包含了作者做到的所有的容错和模型转化
//几个重要的点:
//关联对象kClassPropertiesKey:(用来保存所有属性信息的NSDictionary)
//关联对象kClassRequiredPropertyNamesKey:(用来保存所有属性的名称的NSSet)
//关联对象kMapperObjectKey:(用来保存JSONKeyMapper):自定义的mapper,具体的方法就是用来自定义修改接受数据中的key
//JSONModelClassProperty:封装的jsonmodel的一个属性,它包含了对应属性的名字:(例如 name:gender),类型(例如 type:NSString),是否是JSONModel支持的类型(isStandardJSONType:YES/NO),是否是可变对象(isMutable:YES/NO)等属性。
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{//check for nil input//1.第一步判断传入的是否为nilif (!dict) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//invalid input, just create empty instance//2.第二步判断传入的是否为字典类型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;}//create a class instance//3.创建类实例,通过init方法初始化映射propertyself = [self init];if (!self) {//super init didn't succeedif (err) *err = [JSONModelError errorModelIsInvalid];return nil;}//check incoming data structure//4.检查映射结构是否能从我们传入的dict中找到对应的数据,如果不能找到,就返回nil,并且抛出错误if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {return nil;}//import the data from a dictionary//5.根据传入的dict进行数据的赋值,如果赋值没有成功,就返回nil,并且抛出错误。if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {return nil;}//run any custom model validation//6.根据本地的错误来判断是否有错误,如果有错误,就返回nil,并且抛出错误。if (![self validate:err]) {return nil;}//model is valid! yay!//7.前面的判断都通过,返回selfreturn self;
}
  • 方法1-4:都是对错误的发现与处理。
  • 方法5:是真正的mapping。
  • 方法6:是作者给用户自己定义错误的方法,如果复合了用户自己定义的错误,那么即使mapping成功了,也要返回nil。
  • 方法7:成功返回模型对象。

整个代码的执行流程: 首先,在这个模型类的对象被初始化的时候,遍历自身到所有的父类(直到JSONModel为止),获取所有的属性,并将其保存在一个字典里。获取传入字典的所有key,将这些key与保存的所有属性进行匹配。如果匹配成功,则进行kvc赋值。

load方法实现

load方法调用时机

这里我们不得不提一嘴load方法的调用时机:
+load方法会在加载类的时候就被调用,也就是iOS应用启动的时候,就会加载所有的类,就会调用每个类的+load方法。+load 方法会被默认执行,并且是在 main 函数之前执行的。并没有对类做出任何操作的情况下调用。+ load 方法,当类被加载时它会自动被调用。这个调用非常早。如果你实现了一个应用或框架的 + load,并且你的应用链接到这个框架上了,那么 + load 会在 main() 函数之前被调用。如果你在一个可加载的 bundle 中实现了 + load,那么它会在 bundle 加载的过程中被调用。

+(void)load
{static dispatch_once_t once;dispatch_once(&once, ^{// initialize all class static objects,// which are common for ALL JSONModel subclasses@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",@"unsigned int", @"usigned long", @"long long", @"unsigned long long", @"unsigned short", @"char", @"unsigned char",//and some famous aliases@"NSInteger", @"NSUInteger",@"Block"];//valueTransformer是值转换器的意思valueTransformer = [[JSONValueTransformer alloc] init];// This is quite strange, but I found the test isSubclassOfClass: (line ~291) to fail if using [JSONModel class].// somewhat related: https://stackoverflow.com/questions/6524165/nsclassfromstring-vs-classnamednsstring// //; seems to break the unit tests// Using NSClassFromString instead of [JSONModel class], as this was breaking unit tests, see below//http://stackoverflow.com/questions/21394919/xcode-5-unit-test-seeing-wrong-class//总结:在对JSONModel类进行判断isSubclassOfClass:时,后面写的标准类最好用NSClassFromString而不是[JSONModel class]JSONModelClass = NSClassFromString(NSStringFromClass(self));}});
}

init方法

__setup__方法

在initWithDictionary方法中,第三步调用了init方法,我们来看看init方法的实现:

-(void)__setup__
{//if first instance of this model, generate the property list//1.通过objc_getAssociatedObject来判断是否进行过映射property的缓存//如果没有就使用“__inspectProperties”方法进行映射property的缓存//在这里补充下objc_getAssociatedObject作用:获取相关联的对象,通过创建的时候设置的key来获取if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {[self __inspectProperties];}//if there's a custom key mapper, store it in the associated object//2.判断一下,当前的keyMapper是否存在和是否进行映射过,如果没有进行映射就使用AssociateObject方法进行映射id mapper = [[self class] keyMapper];if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {//3.进行AssociateObject映射objc_setAssociatedObject(self.class,&kMapperObjectKey,mapper,OBJC_ASSOCIATION_RETAIN // This is atomic);}
}-(id)init
{self = [super init];if (self) {//do initial class setup[self __setup__];}return self;
}

__inspectProperties:方法

key mapper主要是用来针对某些json字段名和model数据名不一致的情况。__setup__方法中调用了一个__inspectProperties:方法,这个方法是这个框架的核心方法之一:它的任务是保存了所有需要赋值的属性,用作将来与传进来的字典进行映射。具体代码实现如下:

//inspects the class, get's a list of the class properties
//__setup__中调用这个方法;检查类,得到类属性的列表;它的任务是保存所有需要赋值的属性
-(void)__inspectProperties
{//JMLog(@"Inspect class: %@", [self class]);//    最终保存所有属性的字典,形式为(例子)://    {//        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];//temp variables for the loops//循环的临时变量Class class = [self class];NSScanner* scanner = nil;NSString* propertyType = nil;// inspect inherited properties up to the JSONModel class//检查继承的属性直到JSONModel类;循环条件:当class是JSONModel自己的时候不执行while (class != [JSONModel class]) {//JMLog(@"inspecting: %@", NSStringFromClass(class));//属性的个数unsigned int propertyCount;//获得属性列表(所有@property声明的属性)objc_property_t *properties = class_copyPropertyList(class, &propertyCount);//loop over the class properties//循环遍历所有的属性for (unsigned int i = 0; i < propertyCount; i++) {//JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];//get property name//获取属性名objc_property_t property = properties[i];   //获取当前属性const char *propertyName = property_getName(property);   //name(c字符串)p.name = @(propertyName);   //propertyNeme:属性名称,例如:name,age,gender//JMLog(@"property: %@", p.name);//get property attributes//获取属性属性const char *attrs = property_getAttributes(property);NSString* propertyAttributes = @(attrs);NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];//ignore read-only properties//说明是只读属性,不做任何操作if ([attributeItems containsObject:@"R"]) {continue; //to next property;到下一个属性}// 实例化一个scanner(扫描仪,用于查找相关字符串等功能)scanner = [NSScanner scannerWithString: propertyAttributes];//JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);[scanner scanUpToString:@"T" intoString: nil];[scanner scanString:@"T" intoString:nil];//check if the property is an instance of a class//检查属性是否是类的实例if ([scanner scanString:@"@\"" intoString: &propertyType]) {//属性是一个对象[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]intoString:&propertyType];//JMLog(@"type: %@", propertyClassName);//设置property的类型p.type = NSClassFromString(propertyType);// 判断并设置property的是否是可变的p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);// 判断property的是否我们允许的json类型p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//read through the property protocols//通读属性协议,解析protocol的stringwhile ([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];}}//check if the property is a structure;检查属性是否为structureelse if ([scanner scanString:@"{" intoString: &propertyType]) {//属性是结构体[scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]intoString:&propertyType];p.isStandardJSONType = NO;p.structName = propertyType;}//the property must be a primitive;属性必须是基本类型,比如int float等else {//the property contains a primitive data type//该属性包含基元数据类型[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]intoString:&propertyType];//get the full name of the primitive type//获取基元类型的全名propertyType = valueTransformer.primitivesNames[propertyType];if (![allowedPrimitiveTypes containsObject:propertyType]) {//type not allowed - programmer mistaken -> exception//类型不允许 - 程序员错误 - >异常@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);// 判断property是不是Optionalif([[self class] propertyIsOptional:nsPropertyName]){p.isOptional = YES;}// 判断property是不是Ignoredif([[self class] propertyIsIgnored:nsPropertyName]){p = nil;}//集合类Class customClass = [[self class] classForCollectionProperty:nsPropertyName];if (customClass) {p.protocol = NSStringFromClass(customClass);}//few cases where JSONModel will ignore properties automatically//很少有JSONModel会自动忽略属性的情况,忽略blockif ([propertyType isEqualToString:@"Block"]) {p = nil;}//add the property object to the temp index//将属性对象添加到临时索引,如果字典里不存在,则添加到属性字典里if (p && ![propertyIndex objectForKey:p.name]) {[propertyIndex setValue:p forKey:p.name];}// generate custom setters and getter//生成自定义setter和getterif (p){//name -> Name(大写p的名字属性的前两个字母)NSString *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);//ascend to the super of the class//(will do that until it reaches the root class - JSONModel)//升到class上的最高级//(将这样做,直到它到达根类-JSONModel)再指向自己的父类,直到等于JSONModel才停止class = [class superclass];}//finally store the property index in the static property index//最后将属性索引(属性列表)存储在静态属性索引中//(最后保存所有当前类,JSONModel的所有的父类的属性)objc_setAssociatedObject(self.class,&kClassPropertiesKey,[propertyIndex copy],OBJC_ASSOCIATION_RETAIN // This is atomic);
}

这个方法的大概过程:
1.先是获取当前class的property列表和个数
2.然后再遍历这些property
3.把我们的property通过一个局部变量进行赋值–JSONModelClassProperty,这个是JSONModel提供的类,来解析每个property。
4.获取property的名称给当前这个局部变量
5.获取这个property的属性
6.扫描property属性
7.设置property的类型
8.判断并设置property的是否是可变的
9.判断property的是否我们允许的json类型
10.解析protocol的string
11.检查property是否为structure
12.判断property是不是Optional
13.判断property是不是Ignored
14.判断property是不是只读属性
15.通过kvc去设置相应的值
16.使用AssociateObject进行缓存

接下来,我们来看initWithDictionary的第四步:

__doesDictionary方法

//model类里面定义的属性集合是不能大于传入的字典里的key集合的。
//如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。
//(例如将gender转换为了sex)。
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{//check if all required properties are present//1.检查一下所有的必要属性都存在,并且把他们都放入set中NSArray* 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__]) {//被转换成的属性名称(例如)gender(模型内) -> sex(字典内)transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//check if exists and if so, add to incoming keys//检查是否存在,如果存在,则添加到传入密钥//(例如)拿到sex以后,查看传入的字典里是否有sex对应的值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的所有property是否都能够被赋值,如果不能则说明缺少值则抛出错误。这边主要就是使用了NSSet,将dictionary的所有key存入一个set:incomingKeys,并且将key mapper映射名进行替换。将刚解析出来的model所有property的name也存入一个set:requiredProperties,判断两者是不是包含关系。
此外,如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。

接下来,我们来看initWithDictionary的第五步:

__importDictionary方法

根据传入的dict进行数据的赋值,如果赋值没有成功,就返回nil,并且抛出错误。

//作者在最后给属性赋值的时候使用的是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 primitivesif (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 namesourceClass]; //source nameSEL 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) handle "all other" cases (if any)//3.4)处理“所有其他”情况(如有)if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];}}}}return YES;
}

循环遍历model的每一个解析出来的property结构,首先从dictioanry拿出真正对应property的value,进行value一系列的值判断。value可用的情况下,就开始进行赋值,有setter方法的通过setter方法赋值,基础类型int,float等直接赋值,如果property又是一个JSONModel,就递归先将子Model进行整体解析。如果包含protocol字段,则表明内部是一个array或者dictionary,并包含这个protocol字段的对象解析。对于其他情况,应该是一种类型的转换,通过获取值类型和property类型,调用相应的转换方法进行赋值。

  • 作者在最后给属性赋值的时候使用的是kvc的setValue:ForKey:的方法。
  • 作者判断了模型里的属性的类型是否是JSONModel的子类,可以看到作者的考虑是非常周全的。
  • 整个框架看下来,有很多的地方涉及到了错误判断,作者将将错误类型单独抽出一个类(JSONModelError),里面支持的错误类型很多,体现了作者思维的缜密。而且这个做法也可以在我们写自己的框架或者项目中使用。

JSONValueTransformer的类型转化:

NSMutableString <-> NSString 
NSMutableArray <-> NSArray 
NS(Mutable)Array <-> JSONModelArray 
NSMutableDictionary <-> NSDictionary 
NSSet <-> NSArray 
BOOL <-> number/string 
string <-> number 
string <-> url 
string <-> time zone 
string <-> date 
number <-> date

JSONModel的优点:

  • 命名自动匹配—-model的属性名称和服务器返回的一致,比如关键字id我们可以使用keyMapper了来映射成其他的属性名称。
  • model中可以关联其他的model,只要指定对应的自身的类型
  • model中可以集合其他的model集合,这样必须要实现@protocol协议
  • 在一个Model中获取服务器返回数据不同层级的数据
  • 可以设置全局键映射
  • 可以设置下划线自动转化为驼峰
  • 可以设置可选属性、忽略属性
  • 设置所有属性为可选也表示可以所有可选属性为nil
  • 可以使用内置的HTTP链接
  • 自定义数据处理,内嵌转换比如类型之间的转换
  • 可以自定义处理特殊的属性
  • 可以自定义JSON的校验

带注释版JSONModel源码

相关文章:

【iOS】—— JSONModel源码学习

JSONModel 文章目录JSONModel关于JSONModel的用法initWithDictionary等方法load方法实现load方法调用时机init方法__setup__方法__inspectProperties:方法__doesDictionary方法__importDictionary方法关于JSONModel的用法 可以参考之前写的博客&#xff1a;【iOS】—— JSONMo…...

单片机怎么实现真正的多线程?

所谓多线程都是模拟的&#xff0c;本质都是单线程&#xff0c;因为cpu同一时刻只能执行一段代码。模拟的多线程就是任务之间快速切换&#xff0c;看起来像同时执行的样子。据说最近有多核的单片机&#xff0c;不过成本应该会高很多。对于模拟的多线程&#xff0c;我知道的有两种…...

【LeetCode】剑指 Offer(23)

目录 题目&#xff1a;剑指 Offer 46. 把数字翻译成字符串 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 46. 把…...

[免费专栏] 汽车威胁狩猎之不应该相信的几个威胁狩猎误区

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 汽车威胁狩猎专栏长期更新&#xff0c;本篇最新内容请前往&#xff1a; …...

LinuxFTP文件传输服务和DNS域名解析服务

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

二叉搜索树原理及底层实现

二叉搜索树BST 概念 二叉搜索树又称二叉排序树&#xff0c;它可以是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a;若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值&#xff1b;若它的右子树不为空&#xff0c;则右子树上所有节点的值都…...

python自动化办公(一)

本文代码参考其他教程书籍实现。 文章目录文件读写open函数读取文本文件写入文本文件文件和目录操作使用os库使用shutil库文件读写 open函数 open函数有8个参数&#xff0c;常用前4个&#xff0c;除了file参数外&#xff0c;其他参数都有默认值。file指定了要打开的文件名称&a…...

LeetCode - 198 打家劫舍

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装…...

简单粗暴的分布式定时任务解决方案

分布式定时任务1.为什么需要定时任务&#xff1f;2.数据库实现分布式定时任务3.基于redis实现1.为什么需要定时任务&#xff1f; 因为有时候我们需要定时的执行一些操作&#xff0c;比如业务中产生的一些临时文件&#xff0c;临时文件不能立即删除&#xff0c;因为不清楚用户是…...

蓝桥杯第五天刷题

第一题&#xff1a;数的分解题目描述本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。把 2019 分解成 3 个各不相同的正整数之和&#xff0c;并且要求每个正整数都不包含数字 2和 4&#xff0c;一共有多少种不同的分解方法&…...

Java数组的定义和使用(万字详解)

目录 ​编辑 一. 数组的基本概念 1、什么是数组 2、数组的创建及初始化 1、数组的创建 2、数组的初始化 3、数组的使用 &#xff08;1&#xff09;数组中元素访问 &#xff08;3&#xff09;遍历数组 二、数组是引用类型 1、初始JVM的内存分布 2、基本类型变量与引用类…...

【SpringBoot】自定义Starter

&#x1f6a9;本文已收录至专栏&#xff1a;Spring家族学习之旅 &#x1f44d;希望您能有所收获 一.概述 在使用SpringBoot进行开发的时候&#xff0c;我们发现使用很多技术都是直接导入对应的starter&#xff0c;然后就实现了springboot整合对应技术&#xff0c;再加上一些简…...

【C陷阱与缺陷】----语法陷阱

&#x1f4af;&#x1f4af;&#x1f4af; 要理解一个C程序&#xff0c;必须理解这些程序是如何组成声明&#xff0c;表达式&#xff0c;语句的。虽然现在对C的语法定义很完善&#xff0c;几乎无懈可击&#xff0c;大门有时这些定义与人们的直觉相悖&#xff0c;或容易引起混淆…...

虹科分享| 关于TrueNAS十问十答

上一篇文章我们向您介绍了虹科新品HK-TrueNAS企业存储&#xff0c;很多小伙伴会疑问到底什么是NAS存储&#xff0c;之前常用的磁盘、磁带属于什么存储架构&#xff0c;NAS存储好在哪里&#xff0c;什么时候使用NAS&#xff1f;今天我们整理了关于TrueNAS的十问十答&#xff0c;…...

Https 笔记

HTTP TLS TLS 的前身是 SSL 非对称加密的核心&#xff1a; 两个密钥&#xff08;公私&#xff09; https 需要第三方CA&#xff08;证书授权中心&#xff09;申请SSL证书以确定其真实性 证书种包含了特定的公钥和私钥 密钥交换 自己将私钥上锁后发给对方对方也上锁 在还回来…...

【Python+requests+unittest+excel】实现接口自动化测试框架

一、框架结构&#xff1a; 工程目录 二、Case文件设计 三、基础包 base 3.1 封装get/post请求&#xff08;runmethon.py&#xff09; 1 import requests2 import json3 class RunMethod:4 def post_main(self,url,data,headerNone):5 res None6 if heade…...

MySQL终端的使用及其数据类型的使用

什么是数据库&#xff1f;数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库。每个数据库都有一个或多个不同的 API 用于创建&#xff0c;访问&#xff0c;管理&#xff0c;搜索和复制所保存的数据。我们也可以将数据存储在文件中&#xff0c…...

长视频终局:一场考验资金储备的消耗战

赢者通吃&#xff0c;似乎已成为各行各业的常识&#xff0c;但事实真的是这样吗&#xff1f;20世纪70年代&#xff0c;石油价格高涨&#xff0c;在墨西哥湾油田拍卖中高价拍得油田的企业&#xff0c;要么亏损&#xff0c;要么收入低于预期&#xff0c;但仍然有无数企业在高价竞…...

javaEE初阶 — CSS 常用的属性

文章目录CSS 常用的属性1 字体属性1.1 设置字体家族 font-family1.2 设置字体大小 font-size1.3 设置字体粗细 font-weight1.4 文字倾斜 font-style2 文本属性2.1 文本颜色2.2 文本对齐2.3 文本装饰2.4 文本缩进2.5 行高3 背景属性3.1 背景颜色3.2 背景图片3.3 背景位置3.4 背景…...

【面试题】如何取消 script 标签发出的请求

大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库问题之前在业务上有这样一个场景&#xff0c;通过 script 标签动态引入了一个外部资源&#xff0c;具体方式是这样的const script document.…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识&#xff1a;什么是 B-Tree 和 BTree&#xff1f; B-Tree&#xff08;平衡多路查找树&#xff09; BTree&#xff08;B-Tree 的变种&#xff09; 二、结构对比&#xff1a;一张图看懂 三、为什么 MySQL InnoDB 选择 BTree&#xff1f; 1. 范围查询更快 2…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...