前言
JSONModel是除了Masonry之后的很重要的库,在知乎日报和自己写的项目的网络请求数据层面都用到了它。
一个库开始的学习都是从使用开始,随着学习的知识的增多,现在简单的使用已经不满足了,这篇博客通过参考别人的源码解析,学习JSONModel是如何实现的,以及里面的方法是什么,学习JSONModel的源码
这里的学习还学习了一些之前不知道的用法,例如转换属性名称和自定义错误类型,都是很实用的方法。
1. JSONModel
简单介绍JSONModel
- JSONModel是一个在iOS应用开发中使用的开源库,它的主要功能是将JSON数据映射到Objective-C对象上。使用JSONModel可以方便地处理从服务器返回的JSON格式的数据,将其转化为Objective-C对象,使得数据的访问和处理变得更加简单。
使用JSONModel
- JSONModel提供了一种简洁的方式来定义数据模型,开发者只需要创建一个继承自JSONModel的类,并在其中定义对应的属性,就可以将JSON数据映射到这个类的对象上。JSONModel支持数据模型之间的嵌套关系,开发者可以通过定义嵌套的JSONModel子类来实现。
一些没有了解过的
- JSONModel还提供了一些高级功能,例如自动验证和转换、缓存、数据类型转换等,使得数据的处理更加方便和可靠。此外,JSONModel还提供了一些扩展,例如JSONAPIModel和JSONHTTP,可以帮助开发者更方便地处理RESTful API的数据。
总之,JSONModel是一个强大的JSON数据映射工具,它可以帮助开发者更轻松地处理JSON格式的数据,提高iOS应用的开发效率和质量。
我是打开了之前写的项目把pods里面的jsonModel库复制一份出来看他的源码,里面分了很多文件,挑一些重要的实现配合别人的博客慢慢解析学习。
1.1 JSONModeld的用法
上面说到我们需要创建一个JSONModel的子类,针对我们的API返回的数据定义正确的类型,就可以在获取数据的时候把JSON数据成功映射成这个类,接着熟悉的OC方法和调用即可完成。
最开始的简单学习JSONModel简单用法
这里的博客写了更详细的用法,很好的方法,当初我初学的时候都不知道,这里简单的介绍一下JSONModel源码解析
1.2 JSONModel的其他方法
- 之前之只是知道简单的数据映射方法,JSONModel还有转换类型和自定义错误的方法,很实用。
1.2.1 转换属性名称
例如接口JSON的传入,你在定义JSONModel数据模型的时候已经准备好了某个数据名称,由于某些不可控的原因,某些传入的JSON数据的名称不符合了,这会就可以使用转换属性名称方法
如下字典的key从url传入的时候变成了imageURL,这会就可以使用这个方法避免数据传入错误
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"url": @"ImgaeURL"}];
}
这样子不会因为JSON的数据和Model里模型定义的不同而发生数据nULL的其他错误情况
1.2.2 3. 自定义错误
JSONModel是一个很强大的框架,除了一些框架里自己处理的错误(比如传入的对象不是字典等),框架的作者也允许我们自己定义属于我们自己的错误。这个就很方便对于JSON数据的传入的时候是否进行模型转换的决定了
方法如下
问题记录
- (BOOL)validate:(NSError *__autoreleasing *)error {
}
我这段代码一直报错[NSPlaceholderString initWithString:]: nil argument"错误
,chat也给我说的不对,问题记录一下 怀疑是error的创建为nil,但是具体的问题还没有发现
//自定义错误类型
- (BOOL)validate:(NSError *__autoreleasing *)error {
if (! [super validate:error]) {
return NO;
}
if (self.date && ![self.date isEqualToString:@"data"]) {
*error = [NSError errorWithDomain:@"date ERROR" code:12 userInfo:nil];
NSError *errorLog = *error;
NSLog(@"%@", errorLog.domain);
return NO;
}
return YES;
}
1.3 源码解析
前面的方法挺好的,就是用的时候还是生疏。
看源码之前先上别人的流程图,配合着流程图看即可
观看了好几篇博客都是主要围绕着initWithDictionary
:error
:展开
在这一个方法里作者做到了所有的容错和模型的转化。
这里就从这个方法开始说起,上面的流程图也是这个方法的模板,源码jump跳转就能进入。
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
这个方法的内部代码实现通过了许多方法嵌套完成,一步一步走,也就是想要成功返回一个对象的模型映射,需要经过重重判断,其中不乏包含自定义错误拦截方法。若能成功也就是模型有效!
1.3.1 -(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
流程概览
```objectivec
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
//方法1. 参数为nil
if (!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. 终于通过了!成功返回model
return self;
}
回顾
看的时候突然忘记了iOS isKindOfClass 和 isMemberOfClass的区别。
既然是笔记,就再次简单复习一下
在 iOS 开发中,isKindOfClass 和 isMemberOfClass 都是 Objective-C 类的方法,用于判断一个对象的类型是否属于某个类或其子类。它们的区别在于:
- isKindOfClass 用于判断一个对象是否是某个类或其子类的实例,如果是则返回 YES,否则返回 NO。例如,如果一个对象是 UIButton 类的实例,那么调用 isKindOfClass 方法传入 UIControl 类型的参数会返回 YES。
- isMemberOfClass 用于判断一个对象是否是某个类的实例,如果是则返回 YES,否则返回 NO。与 isKindOfClass 不同,isMemberOfClass 只能判断对象的精确类型,不能包括其子类。例如,如果一个对象是 UIButton 类的实例,那么调用 isMemberOfClass 方法传入 UIControl 类型的参数会返回 NO。
需要注意的是,isKindOfClass 和 isMemberOfClass 方法都是通过比较对象的类与传入的类进行判断的,因此如果传入的参数为 nil,则两个方法都会返回 NO。此外,对于不同的对象,isKindOfClass 和 isMemberOfClass 方法的执行结果也可能不同,具体取决于对象的类型和传入的参数类型。
总结一下上面的6个方法
- 可以看出第一个方法到第四个方法是对错误的发现和处理。
- 第五个方法是核心方法,检查模型映射
- 第六个方法之前说过的自定义错误方法如果复合了用户自己定义的错误,那么即使mapping成功了,也要返回nil。
- 最后通过了所有方法的检查即可成功到达最后一步,返回Model自身。
过程就是这么个过程,感觉不难,继续看看。
在继续看之前得了解一些JSONModel所持有的数据。
1.3.2 JSONModel所持有的一些数据
学习的博客提到了四种数据类型通过源码看到都是 字符类型。
且都用了 static关键字
- 关联对象kClassPropertiesKey:(用来保存所有属性信息的NSDictionary)
- 关联对象kClassRequiredPropertyNamesKey:(用来保存所有属性的名称NSSet)
- 关联对象kMapperObjectKey:(用来保存JSONKeyMapper):自定义的mapper,具体的使用方法在上面的例子中可以看到。
- JSONModelClassProperty: 封装的jsonmodel的一个属性,它包含了对应属性的名字(name:sex),类型(type:NSString),是否是JSONModel支持的类型(isStandardJSONType:YES/NO),是否是可变对象(isMutable:YES/NO)等属性。
这四个数据类型第一次见。。。。
总结一下如何成功完成模型的映射
- 首先,在这个模型类的对象被初始化的时候,遍历自身到所有的父类(直到JSONModel为止),获取所有的属性,并将其保存在一个字典里。获取传入字典的所有key,将这些key与保存的所有属性进行匹配。如果匹配成功,则进行KVC赋值。
1.3.3 load
在之前的小蓝书学到过,每一个类都有一个类方法叫做load。
- 在 Objective-C 中,每个类都有一个 +load 方法,这个方法会在应用启动时,程序加载这个类时被自动调用。这个方法的主要作用是在类被加载时执行一些初始化操作,常见的用法是注册通知、初始化静态变量等。
所以在讲代码之前看看 在 JSONModel 类中,+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 类型的数组。 - 初始化
allowedPrimitiveTypes
属性,这个属性是一个包含了所有兼容的基本数据类型的数组。 - 初始化
valueTransformer
属性,这个属性是一个 JSONValueTransformer 类的实例。 - 初始化
JSONModelClass
属性,这个属性是一个指向当前类的指针。
它们会被所有的 JSONModel 子类所继承和共享。因此,在子类中初始化这些属性是不必要的,它们已经在父类中被初始化过
每个类都有自己的load方法,在自己的load方法实现一些必要的属性的初始化。
我想说的是load的调用时机,这个也是复习以前的学习
1.3.3.1load方法调用时机
+load
方法是在 Objective-C 运行时加载一个类时自动调用的。它的调用时机是在 main 函数之前,且仅会调用一次,无论这个类被实例化多少次。
当一个 Objective-C 程序启动时,运行时会自动加载每个类的信息。在加载一个类时,运行时会先检查这个类是否实现了 +load 方法,如果实现了,就会在加载时调用它。
注意
- 需要注意的是,这个方法会被所有类的实现都调用,包括那些在当前应用程序中没有使用的类。因此,为了避免在应用程序中不必要的开销,应该尽量将 +load 方法的实现限制在必要的类中。
- 需要特别注意的是,当一个类的分类实现中也实现了 +load 方法时,分类中的 +load 方法并不会覆盖原始类的 +load 方法,而是会在原始类的 +load 方法执行后再执行。因此,如果一个类的多个分类都实现了 +load 方法,它们的执行顺序是不确定的。
1.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__
方法里面,两个if语句里面有无尽的探索。
(1)if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey))
的作用是检查当前类是否已经被解析过。如果没有被解析过,则调用 __inspectProperties 方法
解析类的属性信息并缓存起来。
这里的__inspectProperties:方法是该框架的核心方法之一:它的任务是保存了所有需要赋值的属性。用作在将来与传进来字典进行映射:
(2)通过询问chat获取了更详细的过程解释。
-
objc_getAssociatedObject
是一个用于获取关联对象的函数,它的第一个参数是一个对象,第二个参数是一个void
* 类型的key
,用于标识关联对象。这个函数会返回与给定 key 相关联的对象,如果不存在则返回 nil。 - 在这里,我们使用 self.class 作为第一个参数,表示获取当前类的关联对象。如果关联对象不存在,则说明当前类还没有被解析过,需要调用 __inspectProperties 方法进行解析。
通过chat还了解到关联对象的两个函数objc_getAssociatedObject 和 objc_setAssociatedObject
等函数用于关联对象的机制是 Objective-C 运行时提供的一种方法,它可以在运行时为一个对象关联一些额外的数据,而不需要修改这个对象的定义。这种机制在某些情况下非常方便,比如在 JSONModel 中就用它来缓存类的属性信息。
1.3.4.1 __inspectProperties 方法
__inspectProperties:方法是该框架的核心方法之一:它的任务是保存了所有需要赋值的属性。用作在将来与传进来字典进行映射:
这个方法的源代码是极其的冗杂繁琐,先简单的介绍这个方法是如何实现的,看到对应的代码可能会好受一些吧。
-
__inspectProperties
是 JSONModel 中的一个私有方法,用于检查模型类中的属性,并将其转化为一个可用的 NSDictionary 对象。该方法会遍历模型类的属性,然后解析每个属性的相关信息(如属性名、数据类型、对应的 JSON 字段名等),并将其存储在 NSDictionary 对象中。 - 具体来说,该方法会使用运行时特性获取模型类的属性列表,并为每个属性创建一个
JSONModelProperty 对象
,该对象包含属性名、数据类型、对应的 JSON 字段名等信息。然后,这些JSONModelProperty
对象将存储在一个 NSMutableDictionary 对象中,以属性名作为键,JSONModelProperty
对象作为值。最后,该 NSMutableDictionary
对象将使用objc_setAssociatedObject
方法与模型类关联起来,以便以后可以方便地访问。
看看博客分析的源码过程。
-(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";//使其变为结构体
}
//实例化一个scanner
scanner = [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 pop
objc_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:q
propertyType = 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);
}
//忽略block
if ([propertyType isEqualToString:@"Block"]) {
p = nil;
}
//如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)
if (p && ![propertyIndex objectForKey:p.name]) {
[propertyIndex setValue:p forKey:p.name];
}
//setter 和 getter
if (p)
{ //name ->Name
NSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];
// getter
SEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);
if ([self respondsToSelector:getter])
p.customGetter = getter;
// setters
p.customSetters = [NSMutableDictionary new];
SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);
if ([self respondsToSelector:genericSetter])
p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];
for (Class type in allowedJSONTypes)
{
NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);
if (p.customSetters[class])
continue;
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);
if ([self respondsToSelector:setter])
p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];
}
}
}
free(properties);
//再指向自己的父类,知道等于JSONModel才停止
class = [class superclass];
}
//最后保存所有当前类,JSONModel的所有的父类的属性
objc_setAssociatedObject(
self.class,
&kClassPropertiesKey,
[propertyIndex copy],
OBJC_ASSOCIATION_RETAIN
);
}
其中有一些值得注意的点:
- 作者利用一个while函数,获取当前类和当前类的除JSONModel的所有父类的属性保存在一个字典中。在将来用于和传入的字典进行映射。
- 作者用JSONModelClassProperty类封装了JSONModel的每一个属性。这个类有两个重要的属性:一个是name,它是属性的名称(例如gender)。另一个是type,它是属性的类型(例如NSString)。
- 作者将属性分为了如下几个类型:
(1) 对象(不含有协议)。
(2) 对象(含有协议,属于模型嵌套)。
(3) 基本数据类型。
(4) 结构体。
1.3.4.2 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:值转换器。
紧接着地到第四个方法 -(BOOL)__doesDictionary:(NSDictionary)dict matchModelWithKeyMapper:(JSONKeyMapper)keyMapper error:(NSError**)err**
1.3.5 __doesDictionary方法
和__inspect方法类似,他也是JSONModel的一个私有方法。通过方法名字发现他是一个返回了BOOL类型的方法。
__doesDictionary
方法是JSONModel
中的一个私有方法**,用于判断一个对象是否可以被转化为NSDictionary类型。如果可以,该方法会返回YES,否则返回NO。**
具体实现是通过使用Objective-C的runtime机制获取对象的类信息,然后判断该类是否实现了NSDictionary类型的allKeys方法。如果实现了,说明该对象可以被转化为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
//拿到字典里所有的key
NSArray* incomingKeysArray = [dict allKeys];
NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
//从array拿到set
NSSet* 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;
}
注意
- 这里的源代码就发现了这个方法里面的实现涉及到了上述的转换属性名称方法,就是编译器可以通过判断我们有没有自定义mapper,如果有了就按照我们的需要改变
- model类里面定义的属性集合是不能大于传入的字典里的key集合的。(我们所写的接受数据的属性的数量需要小于等于传入的字典的key集合)
当完成了上面的四个方法之后,就会到了最后的核心方法,因为方法6的自定义错误是选择性实现的,若是通过这个方法的判断那么我们就即将完成模型和字典的映射了。
1.3.6 __importDictionary
同样也是返回BOOL类型的方法,很重要的一步。
__importDictionayr
方法是JSONModel内部用于将NSDictionary对象转换为JSONModel对象的方法。
当JSONModel需要将NSDictionary对象转换为JSONModel对象时,会调用__importDictionary
方法。该方法首先会通过classProperties
属性获取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
//检查可选属性
//字典不存在对应的key
if (isNull(jsonValue)) {
//skip this property, continue with next property
//跳过此属性,继续下一个属性
//如果这个key是可以不存在的
if (property.isOptional || !validation) continue;
//如果这个key是必须有的,则返回错误
if (err) {
//null value for required property
//所需属性的值为null
NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
//获取,取到的值的类型
Class jsonValueClass = [jsonValue class];
BOOL isValueOfAllowedType = NO;
//查看是否是本框架兼容的属性类型
for (Class allowedType in allowedJSONTypes) {
if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
isValueOfAllowedType = YES;
break;
}
}
//如果不兼容,则返回NO,mapping失败,抛出错误
if (isValueOfAllowedType==NO) {
//type not allowed
JMLog(@"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对象,并进行一些类型转换和校验。
如果能完成这个方法,那么就成功的实现了JSONModel字典模型转化的过程了!
到底什么是JSONKeyMapper?
一直看源码,其中出现了很多的KeyMapper/Mapper,在看源码之前的属性转化方法还记得吧,一旦Model的名称和字典的不对应就有可能出现NULL或者请求失败。
在使用 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 实例。文章来源:https://www.toymoban.com/news/detail-405462.html
总结
JSONModel是推荐学习的第一个源码库,感觉是因为逻辑比较简单吧,也是用OC得一些方法实现的,虽然有的方法没有见过,底层的实现也很麻烦,但是我只要知道JSONModel的实现过程的6步方法的原理觉得学的很多了,后续还会学习一些感兴趣的源码,这个算我认为是比较简单的了,多加复习才能记住!文章来源地址https://www.toymoban.com/news/detail-405462.html
到了这里,关于【iOS-JSONModel源码】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!