JSONModel源码解析

背景介绍

现在的移动App基本上免不了和网络传输打交道,我们经常需要和服务器进行数据的传输,常用的数据格式无外乎XML或者JSON。这也就引出了一个新的话题,如何将获得的数据实际应用到我们的App中?答案很简单,建立Model。我们拿到的网络传输数据,无非就是一种以JSON格式标准划分的字符串罢了,我们需要将字符串解析到相应的字段中来,否则如果每次我们都需要直接和网络字符串打交道,那也太复杂了。在刚学习iOS开发的时候,那个时候看了Stanford老爷爷讲的开发教程,里面在涉及Core Data的章节曾经写过这样的一段代码:

+ (XXXModel *)insertModelWithDictionary:(NSDictionary *)dict inContext:(NSManagedObjectContext *)context
{
    if (dict == nil || ![dict isKindOf[NSDictionary class]]) return nil;

    XXXModel *model = [XXXModel getByIdentifier:[dict objectForKey:@"id"] inContext:context];
    if (!model) {
        model = [NSEntityDescription insertNewObjectForEntityForName:@"XXXModel" 
                                              inManagedObjectContext:context];
    }

    model.identifier = [dict objectForKey:@"id"];
    model.name       = [dict objectForKey:@"name"];
    model.age        = [dict objectForKey:@"age"];

    // 许多许多属性 
    ......
    ......
    ......

    return model;
}

+ (XXXModel *)getByIdentifier:(NSString *)identifier inContext:(NSManagedObjectContext *)context
{
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XXXModel"];
    [request setPredicate:[NSPredicate predicateWithFormat:@"identifier == %@", identifier]];

    XXXModel *model = [[context executeFetchRequest:request error:nil] lastObject];
    return model;
}

当时一直以为这个是圣经式的写法,一直坚持着,直到后来我接触到了Mantle。当然,今天这篇文章说的不是Mantle,而是另外一个类似的开源库:JSONModel

JSONModel

既然要解析它,首先先放上他的代码地址以示尊重:点我点我!

从Github上的介绍来看,利用JSONModel,我们可以节省大量的时间,它会利用类似于反射(Introspect)的机制,帮助我们自动将JSON的数据解析成Model的字段。

它的用法也很简单,比如有如下这样的JSON数据:

{"id":"10", "country":"Germany", "dialCode": 49, "isInEurope":true}

你只需要建立如下这样的Model:

#import "JSONModel.h"

@interface CountryModel : JSONModel

@property (assign, nonatomic) int id;
@property (strong, nonatomic) NSString* country;
@property (strong, nonatomic) NSString* dialCode;
@property (assign, nonatomic) BOOL isInEurope;

@end

然后像这样:

NSString* json = (fetch here JSON from Internet) ... 
NSError* err = nil;
CountryModel* country = [[CountryModel alloc] initWithString:json error:&err];

嘿嘿,JSON的数据就成功得被解析到country这个变量的对应字段上了!

是不是很神奇?让我们赶快来一探背后的原理吧!!!

源码分析

下载源代码,我们可以看到如下格式的包内容:

-- JSONModel
   -- JSONModel.h/.m
   -- JSONModelArray.h/.m
   -- JSONModelClassProperty.h/.m
   -- JSONModelError.h/.m
-- JSONModelCategories
   -- NSArray+JSONModel.h/.m
-- JSONModelNetworking
   -- 略
-- JSONModelTransformations
   -- JSONKeyMapper.h/.m
   -- JSONValueTransformer.h/.m

我们把JSONModelNetworking中的内容略过,因为基本就是网络传输的东西,不是我们了解的重点。

JSONModel文件

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;

粗略一看,四个初始化方法,太可怕了。但是我们知道在iOS的设计理念里,有一种designated initializer的说法,因此,我们挑一个initWithDictionary看起。

initWithDictionary
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
    1. //check for nil input
    if (!dict) {
        if (err) *err = [JSONModelError errorInputIsNil];
        return nil;
    }

    2. //invalid input, just create empty instance
    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. //create a class instance
    self = [self init];
    if (!self) {

        //super init didn't succeed
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }
    // Class infoClass = NSClassFromString([NSString stringWithFormat:@"%@Info", NSStringFromClass(class)]);
    4. //check incoming data structure
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

    5. //import the data from a dictionary
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }

    6. //run any custom model validation
    if (![self validate:err]) {
        return nil;
    }

    7. //model is valid! yay!
    return self;
}

我们分七步来解析这个函数:

  • 检查参数dict是不是空,是空直接解析失败
  • 检查参数dict是不是NSDictionary或其子类的实例,是空直接解析失败
  • 关键步骤:创建JSONModel,创建失败直接滚粗,千万别小看这一步哦,具体我们下文说
  • 关键步骤:这是一个通过检查是不是有没被映射到的property的过程,如果有存在疏漏的property,那么会导致解析失败,触发Crash
  • 关键步骤:这是真正将解析的值对应的赋予Model的property的阶段
  • 进行自定义的检查工作,比如避免直接触发Crash之类的
  • 得到了正确的model,返回给程序使用。

下面我们来着重分析最重要的三个步骤

重点1: 第三步 [self init]

[self init]的代码调用了init初始化函数,实现如下,我们主要关注的是其中[self setup]

- (id)init
{
    self = [super init];
    if (self) {
        //do initial class setup
        [self __setup__];
    }
    return self;
}

来看看setup的实现,

- (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
                                 );
    }
}

WoW! What are 这个函数弄啥类?看起来很吓人?别怕,让我来一个个解释。

objc_getAssociatedObject想必大家都不会陌生,这就是一个Associate Object,我们经常用这种方法在Category给一个类添加property。

所以,第一个if的意思就是,我根据kClassPropertiesKey这个key,去当前类的Associate Object找property list,如果没找到,就说明是第一次执行解析,所以我要自己建立一个。所以我们赶紧进入
__inspectProperties一探究竟!

- (void)__inspectProperties
{
    //JMLog(@"Inspect class: %@", [self class]);

    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
    while (class != [JSONModel class]) {
        //JMLog(@"inspecting: %@", NSStringFromClass(class));

        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

        //loop over the class properties
        for (unsigned int i = 0; i < propertyCount; i++) {

            JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
            //get property name
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            p.name = @(propertyName);

            //JMLog(@"property: %@", p.name);

            //get property attributes
            const char *attrs = property_getAttributes(property);
            NSString* propertyAttributes = @(attrs);
            NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];

            //ignore read-only properties
            if ([attributeItems containsObject:@"R"]) {
                continue; //to next property
            }

            //check for 64b BOOLs
            if ([propertyAttributes hasPrefix:@"Tc,"]) {
                //mask BOOLs as structs so they can have custom convertors
                p.structName = @"BOOL";
            }

            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];

                //JMLog(@"type: %@", propertyClassName);
                p.type = NSClassFromString(propertyType);
                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"]) {
                        p.isIndex = YES;
                        objc_setAssociatedObject(
                                                 self.class,
                                                 &kIndexPropertyNameKey,
                                                 p.name,
                                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                                 );
                    } else if([protocolName isEqualToString:@"ConvertOnDemand"]) {
                        p.convertsOnDemand = YES;
                    } 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 {

                //the property contains a primitive data type
                [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
                                        intoString:&propertyType];

                //get the full name of the primitive type
                propertyType = valueTransformer.primitivesNames[propertyType];

                if (![allowedPrimitiveTypes containsObject:propertyType]) {

                    //type not allowed - programmer mistaked -> 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;
            }

            //few cases where JSONModel will ignore properties automatically
            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];
            }
        }

        free(properties);

        //ascend to the super of the class
        //(will do that until it reaches the root class - JSONModel)
        class = [class superclass];
    }

    //finally store the property index in the static property index
    objc_setAssociatedObject(
                             self.class,
                             &kClassPropertiesKey,
                             [propertyIndex copy],
                             OBJC_ASSOCIATION_RETAIN // This is atomic
                             );
}

这么多代码,其实总结起来就几个步骤:

  • 获取当前类的的property list,通过class_copyPropertyList runtime的方法
  • 遍历每一个propery,解析他们的属性,这里的属性包括是否只读、类型、是否是weak类型,是否是原子性的等等,如果不了解,可以看如下的表格:

    | Code | Meaning |
    | :————-: |:————-
    | R | The property is read-only (readonly).
    | C | The property is a copy of the value last assigned (copy).
    | & | The property is a reference to the value last assigned (retain).
    | N | The property is non-atomic (nonatomic).
    | G | The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,).
    | S | The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,).
    | D | The property is dynamic (@dynamic).
    | W | The property is a weak reference (__weak).
    | P | The property is eligible for garbage collection.
    | t | Specifies the type using old-style encoding.

  • 根据解析结果,检测是不是合法,如果合法创建对应的JSONModelClassProperty并赋值相应的属性值。

  • 然后重复执行,查看完当前的类就去查询其父类,直到没有为止。

  • 最后将解析出来的property list通过Associate Object给赋值,这和我们刚刚在setup中看到的相呼应。

同样,附上关于property属性的苹果的官方文档链接

这一步基本就解释完了,我们来看看下一步。

重点2: 第四步 __doesDictionary:(NSDictionary)dict matchModelWithKeyMapper:(JSONKeyMapper)keyMapper error:(NSError**)err

老样子,我们先来看看他的实现:

-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
    //check if all required properties are present
    NSArray* incomingKeysArray = [dict allKeys];
    NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];

    //transform the key names, if neccessary
    if (keyMapper || globalKeyMapper) {

        NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];
        NSString* transformedName = nil;

        //loop over the required properties list
        for (JSONModelClassProperty* property in [self __properties__]) {

            transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper importing:YES] : property.name;

            //chek if exists and if so, add to incoming keys
            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
    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;
    }

    //not needed anymore
    incomingKeys= nil;
    requiredProperties= nil;

    return YES;
}

抛开我们暂时还不熟悉的keyMapper(用过Mantle的人估计有一定了解。),整个函数非常容易理解。我们首先获取我们传入的代表JSON数据的Dictionary,然后和我们解析出来的property list进行对比(基于NSSet),如果得到property list室Dictionary的超集,意味着JSON中的数据不能完全覆盖我们生命的属性,说明我们有属性得不到赋值,因此会判断出错。在默认的实现中,如果出现没匹配的实现,就是导致Crash

重点2: 第五步 __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
{
    //loop over the incoming keys and set self's properties
    for (JSONModelClassProperty* property in [self __properties__]) {

        //convert key name ot model keys, if a mapper is provided
        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 ([property.type isSubclassOfClass:[JSONModel class]]) {
                NSMutableDictionary *infoKey = [NSMutableDictionary new];
                for (NSString *name in dict.allKeys) {
                    id value = [dict objectForKey:name];
                    NSMutableString *ignoredCaseName = [NSMutableString stringWithString:[name lowercaseString]];
                    NSString *ignoredCaseKey  = [property.name lowercaseString];

                    NSRange range = [ignoredCaseName rangeOfString:ignoredCaseKey];
                    if (range.location != NSNotFound) {
                        [ignoredCaseName deleteCharactersInRange:range];
                        NSString *newPropertyName = [ignoredCaseName copy];
                        if (!isNull(value)) {
                            [infoKey setObject:value forKey:newPropertyName];
                        }
                    }
                }

                jsonValue = [infoKey copy];
            }

            if (err && isNull(jsonValue)) {
                //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;

        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
            if ([self __customSetValue:jsonValue forProperty:property]) {
                //skip to next JSON key
                continue;
            };

            // 0) handle primitives
            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
            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 transofrm for it
                if (property.protocol) {

                    //JMLog(@"proto: %@", p.protocol);
                    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
                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
                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
                    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
                        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 mistaked -> 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;
}

这一个函数看着吓人,其实非常容易理解。根据我们刚刚得到的property list,我们一个个取出来,用property name作为key,来查询在对应的JSON字典中的value。然后分为如下几个情况:

  • 检查是不是空值。如果该属性是optional,那么无所谓。如果不能为空,那么抛出错误。
  • 检查这个值是不是合法的JSON类型,如果不是,抛出错误。
  • 如果property是非自定义JSONModel子类的字段,基于Key-Value赋值,当然,你可以自己override setter。
  • 如果是自定义的JSONModel子类是,创建一个对应的新类,解析对应的value。
  • 如果再不行,进行一系列的判断和利用JSONValueTransformer进行类型转换进行解析。

为什么要进行JSONValueTransformer的转换呢,是因为在iOS的视线中,由于抽象工厂的存在,构建了大量的簇类,比如NSArray, NSNumber, NSDictionary等等,他们只是对外暴露的一层皮,实质上底层对于真正的类。比如NSArrayI <=> NSArray等等。因此,我们需要通过JSONValueTransformer得到真正的Class Type,同时通过class Type找到最合适的转换方法,在JSONValueTransformer.m的文件中,我们能找到一大堆xxxFromYYY的函数。

好了,到此为止,JSONModel的源码解读就差不多了,下周带来SDWebImageCache的解读。