【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的用法 可以参考之前写的博客:【iOS】—— JSONMo…...
单片机怎么实现真正的多线程?
所谓多线程都是模拟的,本质都是单线程,因为cpu同一时刻只能执行一段代码。模拟的多线程就是任务之间快速切换,看起来像同时执行的样子。据说最近有多核的单片机,不过成本应该会高很多。对于模拟的多线程,我知道的有两种…...
【LeetCode】剑指 Offer(23)
目录 题目:剑指 Offer 46. 把数字翻译成字符串 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 46. 把…...
[免费专栏] 汽车威胁狩猎之不应该相信的几个威胁狩猎误区
也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 汽车威胁狩猎专栏长期更新,本篇最新内容请前往: …...
LinuxFTP文件传输服务和DNS域名解析服务
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放࿰…...
二叉搜索树原理及底层实现
二叉搜索树BST 概念 二叉搜索树又称二叉排序树,它可以是一棵空树,或者是具有以下性质的二叉树:若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;若它的右子树不为空,则右子树上所有节点的值都…...
python自动化办公(一)
本文代码参考其他教程书籍实现。 文章目录文件读写open函数读取文本文件写入文本文件文件和目录操作使用os库使用shutil库文件读写 open函数 open函数有8个参数,常用前4个,除了file参数外,其他参数都有默认值。file指定了要打开的文件名称&a…...
LeetCode - 198 打家劫舍
目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 198. 打家劫舍 - 力扣(LeetCode) 题目描述 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装…...
简单粗暴的分布式定时任务解决方案
分布式定时任务1.为什么需要定时任务?2.数据库实现分布式定时任务3.基于redis实现1.为什么需要定时任务? 因为有时候我们需要定时的执行一些操作,比如业务中产生的一些临时文件,临时文件不能立即删除,因为不清楚用户是…...
蓝桥杯第五天刷题
第一题:数的分解题目描述本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。把 2019 分解成 3 个各不相同的正整数之和,并且要求每个正整数都不包含数字 2和 4,一共有多少种不同的分解方法&…...
Java数组的定义和使用(万字详解)
目录 编辑 一. 数组的基本概念 1、什么是数组 2、数组的创建及初始化 1、数组的创建 2、数组的初始化 3、数组的使用 (1)数组中元素访问 (3)遍历数组 二、数组是引用类型 1、初始JVM的内存分布 2、基本类型变量与引用类…...
【SpringBoot】自定义Starter
🚩本文已收录至专栏:Spring家族学习之旅 👍希望您能有所收获 一.概述 在使用SpringBoot进行开发的时候,我们发现使用很多技术都是直接导入对应的starter,然后就实现了springboot整合对应技术,再加上一些简…...
【C陷阱与缺陷】----语法陷阱
💯💯💯 要理解一个C程序,必须理解这些程序是如何组成声明,表达式,语句的。虽然现在对C的语法定义很完善,几乎无懈可击,大门有时这些定义与人们的直觉相悖,或容易引起混淆…...
虹科分享| 关于TrueNAS十问十答
上一篇文章我们向您介绍了虹科新品HK-TrueNAS企业存储,很多小伙伴会疑问到底什么是NAS存储,之前常用的磁盘、磁带属于什么存储架构,NAS存储好在哪里,什么时候使用NAS?今天我们整理了关于TrueNAS的十问十答,…...
Https 笔记
HTTP TLS TLS 的前身是 SSL 非对称加密的核心: 两个密钥(公私) https 需要第三方CA(证书授权中心)申请SSL证书以确定其真实性 证书种包含了特定的公钥和私钥 密钥交换 自己将私钥上锁后发给对方对方也上锁 在还回来…...
【Python+requests+unittest+excel】实现接口自动化测试框架
一、框架结构: 工程目录 二、Case文件设计 三、基础包 base 3.1 封装get/post请求(runmethon.py) 1 import requests2 import json3 class RunMethod:4 def post_main(self,url,data,headerNone):5 res None6 if heade…...
MySQL终端的使用及其数据类型的使用
什么是数据库?数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。每个数据库都有一个或多个不同的 API 用于创建,访问,管理,搜索和复制所保存的数据。我们也可以将数据存储在文件中,…...
长视频终局:一场考验资金储备的消耗战
赢者通吃,似乎已成为各行各业的常识,但事实真的是这样吗?20世纪70年代,石油价格高涨,在墨西哥湾油田拍卖中高价拍得油田的企业,要么亏损,要么收入低于预期,但仍然有无数企业在高价竞…...
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 标签发出的请求
大厂面试题分享 面试题库前后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库问题之前在业务上有这样一个场景,通过 script 标签动态引入了一个外部资源,具体方式是这样的const script document.…...
蓝桥杯嵌入式(G4系列):RTC时钟
前言: 关于RTC时钟的HAL库配置我也是第一次,之前都是用库函数的写法,这里写下这篇博客来记录一下自己的学习过程。 STM32Cubemx配置: 首先点击左侧的Timers的RTC,勾选以下选项 进入时钟树配置 进入时间设置࿰…...
Linux——进程间通信1
目录 进程间通信目的 进程间通信标准 管道 匿名管道 管道实现进程间通信 管道的特点 进程池 ProcessPool.cc Task.hpp 习题 进程间通信目的 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。 通知事件…...
循环语句——“Python”
各位CSDN的uu们你们好呀,今天小雅兰的内容是Python中的循环语句呀,分为while循环和for循环,下面,让我们进入循环语句的世界吧 循环语句 while循环 for循环 continue和break 循环语句小结 人生重开模拟器 设置初始属性 设置性别…...
Python synonyms查找中文任意词汇的同义词近义词
Python synonyms查找中文任意词汇的同义词近义词 作者:虚坏叔叔 博客:https://xuhss.com 早餐店不会开到晚上,想吃的人早就来了!😄 一、安装 对于非专业的开发人员来说可以简单的使用Python一行代码来找到同义词。这…...
三分钟了解http和https
对应测试人员都会听过http请求和响应.在这里给大家介绍http相关的知识 一.http和https基本概念 HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本…...
docker应用:搭建私有云盘
简介:NextCloud是一个开源的云存储解决方案,可以在自己的服务器上搭建个人云存储系统。它提供了与市面上主流云存储服务(如Dropbox、Google Drive)相似的功能,包括文件存储、共享、同步、协作等。NextCloud的主要优势在…...
【C++进阶】面向对象
程序 编写程序是为了让计算机解决现实生活中的实际问题。pascal之父、结构化程序设计先驱Niklaus Wirth提出程序 算法 数据结构。程序是完成一定功能的一些列有序指令的集合。指令 操作码 指令。将指令按一定的顺序进行整合,就形成了程序。 机器语言与汇编语言…...
从ChatGPT与New Bing看程序员为什么要学习算法?
文章目录为什么要学习数据结构和算法?ChatGPT与NEW Bing 的回答想要通关大厂面试,就不能让数据结构和算法拖了后腿业务开发工程师,你真的愿意做一辈子CRUD boy吗?对编程还有追求?不想被行业淘汰?那就不要只…...
SpringBoot-实用开发篇
SpringBoot开发实用篇开发实用篇中因为牵扯到SpringBoot整合各种各样的技术,所以在整合每一个技术之前,都会做一个快速的普及,这样的话内容整个开发实用篇所包含的内容就会比较多。在学习的时候,如果对某一个技术不是很清楚&#…...
Python进阶-----高阶函数->filter() 函数
目录 前言: filter() 函数介绍 filter() 函数使用示例 1.与循环对比 2.与lambda函数综合使用 3.使用None过滤False 4.过滤字典相关数据 前言: 家人们,当你们获取了一个序列的时候,想要把一些内容去掉,保留一部分…...
网站建设公司投诉电话/友链交换平台源码
数学模型论文地中海鲨鱼问题PAGEPAGE 9摘 要捕食者——食饵模型是数学生态研究的重要内容,影响种群的波动的因素有很多,之神阻滞作用就是其中重要的一种因素。捕食者和食饵这两个物种之间既相互制约又相互依存。首先,本题要求研究两种模型&am…...
郑州做设计公司网站/腾讯企点客服
本文参考数据挖掘与R第二章节 读入数据 方法1,下载Data mining with r的配套包 install.packages(DMwR) 方法2,下载txt数据,并且读入数据。方法见上文。 Summary()#的到数据的摘要,概括。(包括最大,小值&…...
广东网站建设报价如何/做竞价推广大概多少钱
Dubbo的启动主要是发布服务的过程,起到核心作用的就是ServiceConfig(ServiceConfig就是我们在Dubbo的配置文件中配置的dubbo:service这些配置项对应的实体类)。服务的启动初始位置也基本是在这里,下面我们来看看具体的实现内容。 …...
深圳北网站建设/站长工具高清吗
一、什么是范式 好的数据库设计对数据的存储性能和后期的程序开发,都会产生重要的影响。 建立科学的,规范的数据库就需要满足一些规则来优化数据的设计和存储,这些规则就称为范式。 符合三大范式的数据库表,消除了数据冗余、更…...
id中怎么链接wordpress/站长工具四叶草
如果我们已经了解语义网的相关知识,那么接下来继续深入Aperture框架实现数据解析的机制 Aperture框架内部是基于RDF2Go框架来实现RDF模型的存储引擎,RDF模型涉及语义网的概念,下面我们来看一个简单的XML格式的RDF文件 <rdf:RDFxmlns:rdfht…...
网站做下CDN防护/培训学校招生方案范文
裤子 36 2迟8 鞋子 42/43稍微有点大 上衣 XXXL(XXL略微有点小) 内裤 XXL就可以要不然腰太细转载于:https://www.cnblogs.com/zhangzs000/p/11007380.html...