文章目录
- 前言
- 一、JSONModel使用
- 二、JSONModel其他方法
- 三、源码分析
-
- [- (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err](#- (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err)
-
- [[self init]](#[self init])
- setup
- __inspectProperties
- [- (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper](#- (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper)
- [- (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err](#- (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err)
- 总结
前言
JSONModel是一个很经典的源码库,先前基本把iOS的底层看的差不多了,暑假之前都会进行源码与算法学习
一、JSONModel使用
之前已经详细写过使用,有一些没写到的使用会在后面补充
【iOS】JSONModel的基本使用
二、JSONModel其他方法
转换属性名称
有时候我们的JSONModel的属性名称与传入的字典的键值不对应,我们就需要使用keyMapper来转换JSON键,将模型中的属性转换为KeyMapper中对应的键名,dictionary中的key值为属性名,value为json键名
bash
#import <JSONModel/JSONModel.h>
@interface UserProfile : JSONModel
@property (strong, nonatomic) NSString* userId;
@property (strong, nonatomic) NSString* emailAddress;
@property (strong, nonatomic) NSString* firstName;
@property (strong, nonatomic) NSString* lastName;
@end
设置键映射
为了将JSON键映射到正确的模型属性,你需要在模型类中重写+ (JSONKeyMapper *)keyMapper
方法,指定如何将模型中的属性转换为JSON中的键名
bash
@implementation UserProfile
// 使用 JSONKeyMapper 来定义 JSON 字段与模型属性之间的映射关系
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
@"userId": @"user_id",
@"emailAddress": @"email_address",
@"firstName": @"profile_info.first_name",
@"lastName": @"profile_info.last_name"
}];
}
@end
三、源码分析
先看流程图
看一下源代码的目录,可以看到以下内容
在JSONModel中提供了四种初始化方法
bash
-(instancetype)initWithString:(NSString*)string error:(JSONModelError**)err;
-(instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err;
-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
-(instancetype)initWithData:(NSData *)data error:(NSError **)error;
四种方法大同小异,我们从最经典的-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
讲起
- (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err
还是先查看源代码
bash
-(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;
}
可以看到源代码分为了七步,我们来逐步讲解
- 第一步:首先进行判错操作,查询参数是否为空,如果为空则直接返回
nil
- 第二步:查看参数
dict
是否为字典类型,如果不是则也返回nil
- 第三步:初始化
JSONModel
模型,设置Model的属性集合 - 第四步:查询是否存在
KeyMapper
,如果有则进行底层dict
键名的变换,然后验证Model
中的属性是否都在dict的键名匹配,如果有没有被匹配的则会报错 - 第五步:核心方法,终于到了
dict
的键值与模型的Model
的互相映射 - 第六步:可以重写
[self validate:err]
方法并返回NO,让用户自定义错误并阻拦model
的返回 - 第七步:返回
JSONModel
整个过程看起来并不难,但是涉及到的底层知识点比较多
重点主要在第三步第四步第五步,我们来看一下
但是在讲解之前,我们必须讲一下JSONModel中持有的一些关联对象的数据
- 关联对象
kClassPropertiesKey
:(用来保存所有属性信息的NSDictionary
) - 关联对象
kClassRequiredPropertyNamesKey
:(用来保存所有属性的名称NSSet
) - 关联对象
kMapperObjectKey
:(用来保存JSONKeyMapper
):自定义的mapper
,具体的使用方法在上面的例子中可以看到。
[self init]
先来看一下第三步的源代码
bash
self = [self init];
if (!self) {
//super init didn't succeed
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
[self init]
的代码调用了init初始化函数,实现如下,我们主要关注的是其中[self setup]
bash
- (id)init
{
self = [super init];
if (self) {
//do initial class setup
[self __setup__];
}
return self;
}
setup
来看一下setup的实现
bash
- (void)__setup__
{
//if first instance of this model, generate the property list
// 如果是该模型的第一个实例,则生成属性列表
if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
[self __inspectProperties];
}
//if there's a custom key mapper, store it in the associated object
id mapper = [[self class] keyMapper];
if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
objc_setAssociatedObject(
self.class,
&kMapperObjectKey,
mapper,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
}
我们来逐步分析
第一句代码(!objc_getAssociatedObject(self.class, &kClassPropertiesKey))
查看当前类是否存在关联对象,如果是该模型的第一个实例,则生成属性列表,也就是调用[self __inspectProperties];
检索属性
检索完之后如果存在keyMapper
将keyMapper
也与模型类进行关联
bash
if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
objc_setAssociatedObject(
self.class,
&kMapperObjectKey,
mapper,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
__inspectProperties
bash
-(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
);
}
这是一串十分长的代码,先来大概讲解一下这个方法:
这个方法用于检索JSONModel类中的属性,并将其转化为一个可用的 NSDictionary
对象。该方法会遍历模型类的属性,然后解析每个属性的相关信息(如属性名、数据类型、对应的 JSON 字段名等),并将其存储在 NSDictionary
对象中,也就是上文的propertyIndex
如果我们具体分析代码流程,就会发现:
- 该方法会先使用运行时函数获取
JSONModel
的属性列表
bash
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
- 然后为每个属性创建一个
JSONModelProperty 对象
,也就是下文的p实例
bash
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
- p实例也是我们创建的
dictionary
的value
值
bash
if (p && ![propertyIndex objectForKey:p.name]) {
[propertyIndex setValue:p forKey:p.name];
}
JSONModelProperty
对象中包含了属性名、数据类型、对应的 JSON 字段名等信息,以下代码可见一斑
bash
p.type = NSClassFromString(propertyType);
p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];
- 最后所有的这些
JSONModelProperty
对象都会存储在一个NSMutableDictionary
对象------propertyIndex
中,然后通过objc_setAssociatedObject
与模型进行关联,这一步是因为先前没有设置关联,如果不是第一次实例化这个类就不会调用__inspectProperties
方法
bash
//最后保存所有当前类,JSONModel的所有的父类的属性
objc_setAssociatedObject(
self.class,
&kClassPropertiesKey,
[propertyIndex copy],
OBJC_ASSOCIATION_RETAIN
);
- 同时需要注意当前类会不断沿
superclass
继承链向上检索直到父类为JSONModel
bash
while (class != [JSONModel class]) {
......................
......................
class = [class superclass];
}
- (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper
在这一步,我们先不看源码,我们直译一下这个方法:
字典是否与拥有KeyMapper的模型匹配
那么我们就很容易理解这个方法的作用,就是检查字典与模型是否匹配,在上一个方法中我们将
bash
//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;
}
- 首先获取dict中所有的键名
bash
NSArray* incomingKeysArray = [dict allKeys];
- 其次获取模型类中所有的属性名
bash
NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
这里跳进__requiredPropertyNames
方法看一下:
bash
-(NSMutableSet*)__requiredPropertyNames
{
//fetch the associated property names
NSMutableSet* classRequiredPropertyNames = objc_getAssociatedObject(self.class, &kClassRequiredPropertyNamesKey);
if (!classRequiredPropertyNames) {
classRequiredPropertyNames = [NSMutableSet set];
[[self __properties__] enumerateObjectsUsingBlock:^(JSONModelClassProperty* p, NSUInteger idx, BOOL *stop) {
if (!p.isOptional) [classRequiredPropertyNames addObject:p.name];
}];
//persist the list
objc_setAssociatedObject(
self.class,
&kClassRequiredPropertyNamesKey,
classRequiredPropertyNames,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
return classRequiredPropertyNames;
}
我们先前说了,kClassRequiredPropertyNamesKey
是一个用来保存所有属性的名称NSSet
,因此调用这个方法可以让requiredProperties
获取模型类中的所有属性名
- 将dict中得到的key数组转换为set类型
bash
NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
- 如果存在
keyMapper
或是globalKeyMapper
,则将模型中的属性名转换为KeyMapper
中对应的Value
,也就是将JSONModel
中的属性名转换为Json
键名
bash
transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
具体如何转换可以跳进-(NSString*)__mapString:(NSString*)string withKeyMapper:(JSONKeyMapper*)keyMapper
方法查看
- 最后更新dict的键名集合,这样做是为了保证dict中的每个键名都有对应的有效值,而不是仅仅只有一个key键
bash
incomingKeys = transformedIncomingKeys;
- 最后查看模型类的属性列表是否为JSON键值集合的子集
如果不是则会报错,意味着JSON中的数据不能完全覆盖我们声明的属性,说明我们有属性得不到赋值,因此会判断出错
bash
//check for missing input keys
if (![requiredProperties isSubsetOfSet:incomingKeys]) {
//get a list of the missing properties
[requiredProperties minusSet:incomingKeys];
//not all required properties are in - invalid input
JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);
if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
return NO;
}
- (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
前面进行了许多验证的操作,终于到了将JSON数据导入模型中的操作了
bash
//作者在最后给属性赋值的时候使用的是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;
}
- 首先遍历模型类中的所有属性
bash
for (JSONModelClassProperty* property in [self __properties__])
- 找到JSON字典中与模型类对应的key
bash
NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
- 获取keyvalue,这里不用担心获取为空,因为在上一步验证dict是否与keymapper有匹配的方法中已经验证过了
bash
id jsonValue;
@try {
jsonValue = [dict valueForKeyPath: jsonKeyPath];
}
@catch (NSException *exception) {
jsonValue = dict[jsonKeyPath];
}
- 进行一系列检查操作,就不讲了,最后终于通过KVC将JSON字典中的值赋给了模型类
bash
if (property.type == nil && property.structName==nil) {
//generic setter
//通用setter
//kvc赋值
if (jsonValue != [self valueForKey:property.name]) {
[self setValue:jsonValue forKey: property.name];
}
//skip directly to the next key
//直接跳到下一个键
continue;
}
总结
JSONModel提供了一种方法,
- 得到的JSON数据自动与Model进行匹配
- 还提供了keyMapper将JSON键自动映射到模型的属性
- 还让我们可以自定义错误处理
这里再讲一下JSONModel的实现流程:
方法中通过获取JSONModel
类的属性列表,与传入的JSON
数据自动匹配 ,同时还可以通过KeyMapper修改不相同的映射,如果模型类与JSON
数据字段不匹配则会抛出错误(这里体现为Model中某些必须的属性没有在JSON
数据中找到相应的映射),最后如果类型等都检查成功,则通过KVC将JSON
数据中的value
设置在Model
类的对应的属性上
bash
if (jsonValue != [self valueForKey:property.name]) {
[self setValue:jsonValue forKey: property.name];
这里再总结一下JSONModel的几个优点
- 首先就是Runtime中动态解析Model数据类型,可以实现自动匹配
- 然后在setup方法中还通过
AssociatedObject
实现了已经解析过了Model的属性列表等必须信息的缓存,避免了相同模型的重复解析 - 还有就是KVC附值
- 使用KeyMapper修改不一致的JSON字段与属性
这里还有必要再讲一下KeyMapper,你可以通过重写模型类中的 +keyMapper 方法来自定义键映射。这个方法需要返回一个 JSONKeyMapper 对象,该对象通过初始化方法接收一个字典来定义映射关系。
bash
@interface User : JSONModel
@property (strong, nonatomic) NSString *userId;
@property (strong, nonatomic) NSString *email;
@property (strong, nonatomic) NSString *userDescription;
@end
@implementation User
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
@"userId": @"id",
@"email": @"email_address",
@"userDescription": @"description"
}];
}
@end