JSONModel源码学习
- 前言
- JSONModel的使用
- 最基础的使用
- 转换属性名称
- 自定义错误
- 模型嵌套
- JSONModel的继承
- 源码实现
- initWithDictionary
- init
- __doesDictionary
- importDictionary
- 优点
前言
之前了解过JSONModel
的一些使用方法等,但是对于底层实现并不清楚了解,今天来学习一些JSONModel
的源码流程,本篇博客进行一个记录。
JSONModel的使用
最基础的使用
先给出一个当我们单纯传入字典的时候,转化成模型类的用法:
@interface Person : JSONModel@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, assign) NSInteger age;@end
使用字典来转换为模型:
NSDictionary *dict = @{@"name":@"Jack",@"age":@23,@"gender":@"male",};NSError *error;Person *person = [[Person alloc] initWithDictionary:dict error:&error];
NSLog(@"%@", person);
结果
<Person> [name]: Jack[age]: 23[gender]: male
</Person>
转换属性名称
有时传入的字典中的key发生了变化(比如说接口重构之类的原因)但是模型属性我们并不好改变,这个时候就需要有一个转化功能去修改:
这里举例将gender变为sex,那我们应该怎么操作呢
+ (JSONKeyMapper *)keyMapper {return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"gender":@"sex"}];
}
来看看结果:
自定义错误
我们可以自定义属于我们自己的错误判断,比如说我们要限制Person信息的age不能小于18,需要在模型的实现文件中:
- (BOOL)validate:(NSError *__autoreleasing *)error {if (![super validate: error])return NO;if (self.age < 18) {*error = [NSError errorWithDomain:@"Too young" code:10 userInfo:nil];NSError* errorLog = *error;NSLog(@"%@", errorLog.domain);return NO;}return YES;
}
结果:
会打印错误 信息,同时也不会转化模型
模型嵌套
当我给Person类加一个朋友列表的时候,这个时候就是Person类嵌套一个Friend类,然而这种情况应该怎么操作呢:
#import <Foundation/Foundation.h>
#import <JSONModel/JSONModel.h>
@protocol Friend
@end
NS_ASSUME_NONNULL_BEGIN@interface Friend : JSONModel@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;@end@interface Person : JSONModel
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* gender;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSArray<Friend> *friends;//数组,嵌套模型
@endNS_ASSUME_NONNULL_END
看看结果如何:
这就是几种JSONModel
比较典型的使用,下面来看看底层源码的实现。
JSONModel的继承
笔者看源码发现JSONModel需要扫描父类直至JSONModel这个类,但是嵌套的实现使用的是协议,这里记录一下笔者AI到的继承的使用,若有不对还望指正。
JSONModel的继承是通常适用于返回的数据中都有一段公共字段,我们有很多类型数据的时候,他们都有id和created_at,那么我们可以创建一个BaseModel
,令所有类型的model直接继承这个类型model,这样我们可以避免每个子类重写。
源码实现
先来一张流程图
这里大致讲解一下流程:
首先在这个模型类的对象被初始化的时候,遍历自身到所有的父类,获取所有的属性,将其保存到一个字典中去,获取传入字典的所有的key,将这些key同保存的属性进行匹配。若是匹配成功,就进行KVC赋值
JSONModel
一共提供了四种初始化的方法,如下:
-(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;
这种最经典的方法讲起:
initWithDictionary
先来看看这个方法的源码实现:
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{//检查参数是否为nilif (!dict) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//参数不是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;}//初始化self = [self init];if (!self) {//super init didn't succeedif (err) *err = [JSONModelError errorModelIsInvalid];return nil;}//检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {return nil;}//字典的key与模型的属性的映射if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {return nil;}//可以重写[self validate:err]方法并返回No,令用户自定义错误去阻拦model的返回if (![self validate:err]) {return nil;}//model is valid! yay!return self;
}
总结:
- 1-4步中都是对错误的发现和处理
- 方法5是真正的mapping
- 方法6是作者自定义错误的方法,若是复合了自定义的错误,即使mapping成功也要返回nil
- 方法7成功返回模型对象
在开始之前,先来了解一下JSONModel
所持有的数据:
static const char * kMapperObjectKey;//自定义的mapper,具体使用方法在上面的例子
static const char * kClassPropertiesKey;//用来保存所有属性信息的NSDictionary
static const char * kClassRequiredPropertyNamesKey;//用来保存所有属性的名称NSSet
static const char * kIndexPropertyNameKey;
KeyMapper的使用
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];
init
我们从第三个方法init开始,看看流程:
-(id)init
{self = [super init];if (self) {//do initial class setup[self __setup__];}return self;
}-(void)__setup__
{//如果第一次实例化,就执行if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {[self __inspectProperties];}//若是存在自定义的mapper,就将其保存在关联对象中,key是KMapperObjectKeyid 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, &kMapperObjectKey)
的时候第一个参数使用self.class
,这就是说这里我们获取当前类的关联对象,要是关联对象不存在,就说明当前类还没有被解析过,需要调用__inspectProperties
方法进行解析
在第一次实例化的时候,所调用的__inspectProperties
也是该框架的核心方法之一:其保存了所有需要赋值的属性,用作在将来与传进来字典进行映射:
具体来说,该方法会使用运行时特性获取模型类的属性列表,并为每个属性创建一个 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]) {//JMLog(@"inspecting: %@", NSStringFromClass(class));//所有属性的个数unsigned int propertyCount;//获取属性列表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";//使其变为结构体} //实例化一个scannerscanner = [NSScanner scannerWithString: propertyAttributes];[scanner scanUpToString:@"T" intoString: nil];[scanner scanString:@"T" intoString:nil]; 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 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];}} 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:qpropertyType = 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);}//忽略blockif ([propertyType isEqualToString:@"Block"]) {p = nil;}//如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)if (p && ![propertyIndex objectForKey:p.name]) {[propertyIndex setValue:p forKey:p.name];}//setter 和 getterif (p){ //name ->NameNSString *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);//再指向自己的父类,知道等于JSONModel才停止class = [class superclass];}//最后保存所有当前类,JSONModel的所有的父类的属性objc_setAssociatedObject(self.class,&kClassPropertiesKey,[propertyIndex copy],OBJC_ASSOCIATION_RETAIN);//使用关联对象与模型类关联起来
}
这里有几点需要注意:
- 作者使用一个
while
函数,获取当前类和当前类的除去JSONModel
的所有父类的属性保存在一个字典中。用来和传入的字典进行一个映射关系。 - 作者使用
JSONModelClassProperty
类封装了JSONModel
的每一个属性,这个类有两个重要的属性:一个是name,这是属性的名称;另一个是type,这是属性的类型 - 作者将属性分为了以下几个类型:
- 对象(不含有协议)
- 对象(含有协议,即模型嵌套)
- 基本数据类型
- 结构体
__doesDictionary
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{//拿到字典中所有的keyNSArray* incomingKeysArray = [dict allKeys];//返回保存所有属性名称的数组(name,age,gender...)NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;//从array中拿到setNSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];//若是用户自定义了mapper,进行一个转换if (keyMapper || globalKeyMapper) {NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];NSString* transformedName = nil;//遍历需要转换的属性列表for (JSONModelClassProperty* property in [self __properties__]) {//被转换成的属性名称 gender(模型内) -> sex(字典内)transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//拿到sex以后,查看传入的字典里是否有sex对应的值@try {value = [dict valueForKeyPath:transformedName];}@catch (NSException *exception) {value = dict[transformedName];}//若是值存在,就将sex添加到传入的keys数组中if (value) {[transformedIncomingKeys addObject: property.name];}}//用映射的键名称覆盖原始传入列表incomingKeys = transformedIncomingKeys;}//查看当前的model的属性的集合是否大于传入的属性集合,如果是,则返回错误。//也就是说模型类里的属性是不能多于传入字典里的key的,例如:if (![requiredProperties isSubsetOfSet:incomingKeys]) {//获取缺失属性的列表(获取多出来的属性)[requiredProperties minusSet:incomingKeys];//并非所有必需的属性都在 in - 输入无效JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];return NO;}//not needed anymoreincomingKeys= nil;requiredProperties= nil;return YES;
}
- 这个方法就将我们使用过程中重写的
keyMapper
方法中的属性名称进行了一个转换,按照我们的需求进行一个改变 - 这里我们可以看到在最后一个if判断中说明,我们传入的字典数据中的key集合不能小于model类的定义的属性集合,也就是说我们model类中定义的属性集合必须要赋值。
importDictionary
终于到了将NSDictionary
对象转化为JSONModel
对象这一步了,来看看会发生什么:
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{//遍历保存的所有属性的字典for (JSONModelClassProperty* property in [self __properties__]) {//将属性的名称拿过来,作为key,用这个key来查找传进来的字典里对应的值NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//用来保存从字典里获取的值id jsonValue; @try {jsonValue = [dict valueForKeyPath: jsonKeyPath];}@catch (NSException *exception) {jsonValue = dict[jsonKeyPath];}//字典不存在对应的keyif (isNull(jsonValue)) {//如果这个key是可以不存在的if (property.isOptional || !validation) continue; //如果这个key是必须有的,则返回错误if (err) {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 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;}//如果是兼容的类型:if (property) {// 查看是否有自定义setter,并设置if ([self __customSetValue:jsonValue forProperty:property]) {continue;};// 基本类型if (property.type == nil && property.structName==nil) {//kvc赋值if (jsonValue != [self valueForKey:property.name]) {[self setValue:jsonValue forKey: property.name];}continue;}// 如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它if (isNull(jsonValue)) {if ([self valueForKey:property.name] != nil) {[self setValue:nil forKey: property.name];}continue;}// 1. 属性本身是否是jsonmodel类型if ([self __isJSONModelSubClass:property.type]) {//通过自身的转模型方法,获取对应的值JSONModelError* initErr = nil;id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];if (!value) { //如果该属性不是必须的,则略过if (property.isOptional || !validation) continue;//如果该属性是必须的,则返回错误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];}continue;} else {// 如果不是jsonmodel的类型,则可能是一些普通的类型:NSArray,NSString。。。// 是否是模型嵌套(带有协议)if (property.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;}}// 对象类型if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {//可变类型if (property.isMutable) {jsonValue = [jsonValue mutableCopy];}//赋值if (![jsonValue isEqual:[self valueForKey:property.name]]) {[self setValue:jsonValue forKey: property.name];}continue;}// 当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从NSSet到NSArray转换:- (NSSet *)NSSetFromNSArray:(NSArray *)array)if ((![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))||//the property is mutableproperty.isMutable||//custom struct propertyproperty.structName) {Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];//JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",(property.structName? property.structName : property.type), //target namesourceClass]; //source nameSEL selector = NSSelectorFromString(selectorName);//查看自定义的转换器是否存在BOOL foundCustomTransformer = NO;if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES; } else {//try for hidden custom transformerselectorName = [NSString stringWithFormat:@"__%@",selectorName];selector = NSSelectorFromString(selectorName);if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES;}}//如果存在自定义转换器,则进行转换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 { //没有自定义转换器,返回错误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)if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];}}}}return YES;
}
值得注意的是:
- 作者在最后给属性赋值的时候使用的是kvc的
setValue:ForKey:
的方法。 - 作者判断了模型里的属性的类型是否是JSONModel的子类,可见作者的考虑是非常周全的。
- 整个框架看下来,有很多的地方涉及到了错误判断,作者将将错误类型单独抽出一个类(
JSONModelError
),里面支持的错误类型很多。
在设置属性值时,__importDictionary方法还会进行一些类型转换和校验
- 如果属性是JSONModel类型,则将字典转换为该类型的JSONModel对象,并设置到当前属性中。如果属性是基本数据类型(如int、float等),则将字典中的数值转换为相应的数据类型,并设置到当前属性中。
- 如果属性是NSDate类型,则将字典中的时间戳转换为NSDate对象,并设置到当前属性中。总之,__importDictionary方法的主要作用是将NSDictionary对象转换为JSONModel对象,并进行一些类型转换和校验。
这里有一点笔者之前了解不够的地方记录一下:
@property (nonatomic, strong) NSString<Optional> *optionalString;
当我们使用
<Optional>
这个协议标记的时候,说明这个属性不是必须实现的,当我们没有实现这个属性的时候,其被赋值为nil
优点
- Runtime动态解析model数据类型
- keyMapper映射
- KVC赋值
- 使用关联对象来避免重复解析相同模型,很妙