【iOS】JSONModel源码学习

JSONModel源码学习

前言

之前了解过JSONModel的一些使用方法等,但是对于底层实现并不清楚了解,今天来学习一些JSONModel的源码流程,本篇博客进行一个记录。

JSONModel的使用

最基础的使用

先给出一个当我们单纯传入字典的时候,转化成模型类的用法:

objc 复制代码
@interface Person : JSONModel

@property (nonatomic, copy)   NSString *name;
@property (nonatomic, copy)   NSString *sex;
@property (nonatomic, assign) NSInteger age;

@end

使用字典来转换为模型:

objc 复制代码
NSDictionary *dict = @{
                        @"name":@"Jack",
                        @"age":@23,
                        @"gender":@"male",
                      };
 NSError *error;
 Person *person = [[Person alloc] initWithDictionary:dict error:&error];
NSLog(@"%@", person);

结果

c 复制代码
<Person> 
   [name]: Jack
   [age]: 23
   [gender]: male
</Person>

转换属性名称

有时传入的字典中的key发生了变化(比如说接口重构之类的原因)但是模型属性我们并不好改变,这个时候就需要有一个转化功能去修改:

这里举例将gender变为sex,那我们应该怎么操作呢

objc 复制代码
+ (JSONKeyMapper *)keyMapper {
    return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"gender":@"sex"}];
}

来看看结果:

自定义错误

我们可以自定义属于我们自己的错误判断,比如说我们要限制Person信息的age不能小于18,需要在模型的实现文件中:

objc 复制代码
- (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类,然而这种情况应该怎么操作呢:

objc 复制代码
#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;//数组,嵌套模型
@end

NS_ASSUME_NONNULL_END

看看结果如何:

这就是几种JSONModel比较典型的使用,下面来看看底层源码的实现。

JSONModel的继承

笔者看源码发现JSONModel需要扫描父类直至JSONModel这个类,但是嵌套的实现使用的是协议,这里记录一下笔者AI到的继承的使用,若有不对还望指正。

JSONModel的继承是通常适用于返回的数据中都有一段公共字段,我们有很多类型数据的时候,他们都有id和created_at,那么我们可以创建一个BaseModel,令所有类型的model直接继承这个类型model,这样我们可以避免每个子类重写。

源码实现

先来一张流程图

这里大致讲解一下流程:

首先在这个模型类的对象被初始化的时候,遍历自身到所有的父类,获取所有的属性,将其保存到一个字典中去,获取传入字典的所有的key,将这些key同保存的属性进行匹配。若是匹配成功,就进行KVC赋值


JSONModel一共提供了四种初始化的方法,如下:

objc 复制代码
-(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

先来看看这个方法的源码实现:

objc 复制代码
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
    //检查参数是否为nil
    if (!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 succeed
        if (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所持有的数据:

objc 复制代码
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)。
objc 复制代码
JSONKeyMapper *mapper = [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
    @"propertyOne": @"property_one",
    @"propertyTwo": @"property_two"
}];
MyModel *model = [[MyModel alloc] initWithDictionary:jsonDict error:nil];

init

我们从第三个方法init开始,看看流程:

objc 复制代码
-(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是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
                                 );
    }
}

这里我们使用!objc_getAssociatedObject(self.class, &kMapperObjectKey) 的时候第一个参数使用self.class,这就是说这里我们获取当前类的关联对象,要是关联对象不存在,就说明当前类还没有被解析过,需要调用__inspectProperties方法进行解析

在第一次实例化的时候,所调用的__inspectProperties也是该框架的核心方法之一:其保存了所有需要赋值的属性,用作在将来与传进来字典进行映射

具体来说,该方法会使用运行时特性获取模型类的属性列表,并为每个属性创建一个 JSONModelProperty 对象,该对象包含属性名、数据类型、对应的 JSON 字段名等信息。然后,这些 JSONModelProperty 对象将存储在一个 NSMutableDictionary 对象中,以属性名作为键,JSONModelProperty 对象作为值。最后,该 NSMutableDictionary 对象将使用 objc_setAssociatedObject 方法与模型类关联起来,以便以后可以方便地访问。

objc 复制代码
-(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";//使其变为结构体
            }            
            //实例化一个scanner
            scanner = [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 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,这是属性的名称;另一个是type,这是属性的类型
  • 作者将属性分为了以下几个类型:
    • 对象(不含有协议)
    • 对象(含有协议,即模型嵌套)
    • 基本数据类型
    • 结构体

__doesDictionary

objc 复制代码
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
    //拿到字典中所有的key
    NSArray* incomingKeysArray = [dict allKeys];
  	//返回保存所有属性名称的数组(name,age,gender...)
    NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
  	//从array中拿到set
    NSSet* 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 anymore
    incomingKeys= nil;
    requiredProperties= nil;

    return YES;
}
  • 这个方法就将我们使用过程中重写的keyMapper方法中的属性名称进行了一个转换,按照我们的需求进行一个改变
  • 这里我们可以看到在最后一个if判断中说明,我们传入的字典数据中的key集合不能小于model类的定义的属性集合,也就是说我们model类中定义的属性集合必须要赋值。

importDictionary

终于到了将NSDictionary对象转化为JSONModel对象这一步了,来看看会发生什么:

objc 复制代码
-(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];
        }
        //字典不存在对应的key
        if (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 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;
        }
        //如果是兼容的类型:
        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 mutable
                    property.isMutable
                    ||
                    //custom struct property
                    property.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 name
                                              sourceClass]; //source name
                    SEL selector = NSSelectorFromString(selectorName);
                    //查看自定义的转换器是否存在
                    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;
                        }
                    }
                    //如果存在自定义转换器,则进行转换
                    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对象,并进行一些类型转换和校验。

这里有一点笔者之前了解不够的地方记录一下:

objc 复制代码
@property (nonatomic, strong) NSString<Optional> *optionalString; 

当我们使用<Optional>这个协议标记的时候,说明这个属性不是必须实现的,当我们没有实现这个属性的时候,其被赋值为nil

优点

  • Runtime动态解析model数据类型
  • keyMapper映射
  • KVC赋值
  • 使用关联对象来避免重复解析相同模型,很妙