参考博客:
JSONModel源码解析
JSONModel初始化方法
JSONModel对外给出了许多常见的初始化方法:
- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err;
- (instancetype)initWithData:(NSData *)data error:(NSError **)error;
// 创建一个新的模型实例,并使用来自文本参数的JSON初始化它。
// 该方法假定输入文本为UTF8编码。
- (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err;
// 创建一个新的模型实例,并使用使用给定编码的文本参数的JSON初始化它。
- (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err;
让我们来看看其实现:
- (instancetype)initWithData:(NSData *)data error:(NSError *__autoreleasing *)err
{
//check for nil input
if (!data) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//read the json
JSONModelError* initError = nil;
id obj = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&initError];
if (initError) {
if (err) *err = [JSONModelError errorBadJSON];
return nil;
}
//init with dictionary
id 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 input
if (!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;
}
我们发现,这几个初始化方法最终实际上都还是调用了:
- (id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err;
这个方法可以说是核心方法,让我们来看看他的实现如何。
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
//check for nil input
//1.为空判断
if (!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.核心,初始化映射property
self = [self init];
if (!self) {
//super init didn't succeed
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
//check incoming data structure
//4.检查映射结构是否能够从dictionary中找到相应的数据
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
//import the data from a dictionary
//5.进行数据赋值
if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
return nil;
}
//run any custom model validation
//6.本地数据检查
if (![self validate:err]) {
return nil;
}
//model is valid! yay!
return self;
}
主要分为以下6块:
- 1.空值判断
- 2.输入类型dictionary判断
- 3.初始化:解析model对象,并且映射property
- 4.查值:检查model property名与数据来源json字典中数据名,判断是否所有property都有值
- 5.赋值:进行赋值
- 6.本地数据正确性检查
以下我将主要解析3,4,5这三部分的主代码。
初始化
-(void)__setup__
{
//if first instance of this model, generate the property list
//使用AssociateObject进行映射property的缓存,判断是否映射过
if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
[self __inspectProperties];
}
//if there's a custom key mapper, store it in the associated object
//获取对象的keyMapper影射,同样使用AssociateObject进行映射property的缓存
id mapper = [[self class] keyMapper];
if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
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;
}
其中的kClassPropertiesKey和kMapperObjectKey都是JSONModel所持有的数据,如下:
关联对象kClassPropertiesKey:(用来保存所有属性信息的NSDictionary)
关联对象kClassRequiredPropertyNamesKey:(用来保存所有属性的名称NSSet)
关联对象kMapperObjectKey:(用来保存JSONKeyMapper):自定义的mapper,具体的使用方法在上面的例子中可以看到。
JSONModelClassProperty: 封装的jsonmodel的一个属性,它包含了对应属性的名字(name:sex),类型(type:NSString),是否是JSONModel支持的类型(isStandardJSONType:YES/NO),是否是可变对象(isMutable:YES/NO)等属性。
这段代码使用AssociateObject的缓存判断kClassPropertiesKey就知道该model对象是否有进行过解析property,没有的话进行解析,同时取出model的key mapper,也同样进行缓存。
key mapper主要是用来针对某些json字段名和model数据名不一致的情况。
上方代码中__setup__方法中调用了一个__inspectProperties:方法,这个方法是这个框架的核心方法之一:它的任务是保存了所有需要赋值的属性,用作将来与传进来的字典进行映射。具体代码实现如下:
//该__inspectProperties方法是该第三方框架的核心方法之一:它的任务是保存了所有需要赋值的属性。用作在将来与传进来的字典进行映射
//inspects the class, get's a list of the class properties
//检查类,获取类属性列表
-(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);
// 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:@","];
//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];//propertyType -> NSString
//JMLog(@"type: %@", propertyClassName);
p.type = NSClassFromString(propertyType);// p.type = @"NSString"
p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);//判断是否是可变的对象
p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是该框架兼容的类型
//read through the property protocols
//通读属性协议
//存在协议(数组,也就是嵌套模型)
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];
}
}
//check if the property is a structure检查属性是否为结构体
else if ([scanner scanString:@"{" intoString: &propertyType]) {
//属性是结构体
[scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
intoString:&propertyType];
p.isStandardJSONType = NO;
p.structName = propertyType;
}
//the property must be a primitive
//属性是基本类型
else {
//属性是基本类型:Tq、N、V_age
//the property contains a primitive data type
[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
intoString:&propertyType];
//get the full name of the primitive type
//获取基元类型的全名
//propertyType:q
propertyType = valueTransformer.primitivesNames[propertyType];
//propertyType:lone
//基本类型数组
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);
//可选的
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);
}
//few cases where JSONModel will ignore properties automatically
//很少有JSONModel会自动忽略属性的情况
//忽略block
if ([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和getter
if (p)
{
//name -> Name(大写p的名字属性的前两个字母)
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);
//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这是原子的
);
}
简单来说就是:
使用runtime的class_copyPropertyList方法去获得所有model对象的property列表,再使用
property_getAttributes获得property的encode string,通过解析encode string去获得property对象的正确含义。
在解析的过程中,使用NSScanner去扫描encode string,并使用JSONModelClassProperty的结构体去保存相关信息。
其中对于protocol的使用较为特殊,在这里的protocol并非我们平常当作接口抽象的作用,而是单纯的为了让encode string中增加相应的字段,可以在解析与赋值的时候给予特定的含义。
__doesDictionary方法:
//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__]) {
//被转换成的属性名称(例如)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;
}
检查一下所有的必要属性都存在,并且把他们都放入set中 。如果缺少值则抛出错误。
__importDictionary:方法:
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
//loop over the incoming keys and set self's properties
//循环遍历映射出来的JSONModelClassProperty结构体
for (JSONModelClassProperty* property in [self __properties__]) {
//convert key name ot model keys, if a mapper is provided
//keyMapper映射,获取镇真正的值
NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper importing:YES] : 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
if (isNull(jsonValue)) {
//skip this property, continue with next property
if (property.isOptional || !validation) continue;
if (err) {
//null value for required property
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;
//判断数据输入类型是不是允许的json类型
for (Class allowedType in allowedJSONTypes) {
if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
isValueOfAllowedType = YES;
break;
}
}
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方法进行set
if ([self __customSetValue:jsonValue forProperty:property]) {
//skip to next JSON key
continue;
};
// 0) handle primitives
// 代表基础类型,比如int float等,直接使用kvc赋值
if (property.type == nil && property.structName==nil) {
//generic setter
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;
}
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
// 是否包含protocol的字段,该字段主要用来表明array或者dictionary中的对象类型
if (property.protocol) {
//JMLog(@"proto: %@", p.protocol);
//循环遍历子内容,将对应的类型赋给相应的array或者dictionary
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
// 判断标准的json类型,比如nsstring等
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
// 其他处理情况,主要是一些类型转换的情况,比如nsstring转换为nsurl等
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)
// 获取真实的json数据类型
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
// 通过property类型和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) {
//it's OK, believe me...
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//transform the value
// 通过 JSONValueTransformer 进行类型转换
jsonValue = [valueTransformer performSelector:selector withObject:jsonValue];
#pragma clang diagnostic pop
if (![jsonValue isEqual:[self valueForKey:property.name]]) {
[self setValue:jsonValue forKey: property.name];
}
} else {
// it's not a JSON data type, and there's no transformer for it
// if property type is not supported - that's a programmer mistake -> exception
@throw [NSException exceptionWithName:@"Type not allowed"
reason:[NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name]
userInfo:nil];
return NO;
}
} else {
// 3.4) handle "all other" cases (if any)
if (![jsonValue isEqual:[self valueForKey:property.name]]) {
[self setValue:jsonValue forKey: property.name];
}
}
}
}
}
return YES;
}
这是从字典里获取值并赋值给当前模型对象的实现。
操作如下:
循环遍历映射出来的JSONModelClassProperty
2、获取keyMapper的映射,获取真正的值
3、判断数据输入的类型是不是我们允许的json类型
4、检查model中是否有匹配的属性
5、检查自定义的setter,使用对应的set进行赋值—-(这个具体看代码里面的判断,我就不列举出来了)
6、处理一些转化类型,比如string <-> number、string <-> url