jsonModel源码分析

JSONModel是我们要讲解的重点,我们首先从它的初始化方法谈起。

182380-4c4c9ae0a9d4a9d7.jpg
-(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;

designated initializer

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


6941baebjw1eozmnjcsvaj21060ueqbx.jpg

在自己的开发过程中,合理地遵守和运用designated initializer会减少许多重复代码。
并且理解了这一个概念,对整个Cocoa框架的理解也有帮助。 例如UIViewController的Designated initializer是

- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle

但是可能有人会发现,如果你直接使用 [[viewController alloc]init]来生产Controller,且你是使用XIB来组织界面的,那么最后你得到的ViewController的View还是来自XIB的。这背后的原因就是Designated initializer帮你完成了这个工作。
因此,我们挑一个 initWithDictionary看起。
废话不多说直接看源码

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{

    //检查是否为空
    if (!dict) {
        if (err) *err = [JSONModelError errorInputIsNil];
        return 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;
    }
 //create a class instance
    self = [self init];//转入重写的init方法
    。。。。。。。后面的在init之后,先看init方法
}

**************************************************************************************************************
//重写的init方法
-(id)init
{
    self = [super init];
    if (self) {
        //do initial class setup
        [self __setup__];
    }
    return self;
}


**************************************************************************************************************
-(void)__setup__
{
    //if first instance of this model, generate the property list
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {//是否第一次初始化这个模型类,标准是是否绑定了kClassPropertiesKey,kClassPropertiesKey取出来是数组(存放类的成员变量)
        [self __inspectProperties];//去遍历成员变量列表,并把遍历好的放到数组中,以kClassPropertiesKey为键绑定:objc_setAssociatedObject,
    }
    //if there's a custom key mapper, store it in the associated object
    id mapper = [[self class] keyMapper];//取出需要进行转换的成员变量名,比如id-->ID,是重写keyMapper返回的JSONKeyMapper类型
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {//如果有,并且是第一次初始化之前没有进行过绑定
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );//绑定到类上
    }
}

这里需要注意的是 objc_setAssociatedObject这个方法 之前可能很少用到。简单来说类目是给对象增加方法 objc_setAssociatedObject是为类或者对象增加属性
创建关联要使用到Objective-C的运行时函数:objc_setAssociatedObject来把一个对象与另外一个对象进行关联。该函数需要四个参数:源对象,关键字,关联的对象和一个关联策略。当然,此处的关键字和关联策略是需要进一步讨论的。
■ 关键字是一个void类型的指针。每一个关联的关键字必须是唯一的。通常都是会采用静态变量来作为关键字。
■ 关联策略表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;还有这种关联是原子的还是非原子的。这里的关联策略和声明属性时的很类似。这种关联策略是通过使用预先定义好的常量来表示的。 下面是实例代码

//
//  main.m
//  objc_getAssociatedObject
//
//  Created by 何壮壮 on 16/11/4.
//  Copyright © 2016年 何壮壮. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "person.h"
// 表示关联关系的key,主要目的是用来索引
const NSString *associatedKey = @"associate_nsarray_with_nsstring_key";

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        
        
        
        NSArray *array = [[NSArray alloc]initWithObjects:@"1", @"2", @"3", nil];
       
        NSString * overview = @"Hello world";
 // 从array中获取被关联的对象string

 // 注意,这里就没有string这个对象任何事了

 // string其实已经变成了array的一个属性值
        objc_setAssociatedObject(array, &associatedKey, overview, OBJC_ASSOCIATION_RETAIN);
        
        NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &associatedKey);
        
        NSLog(@"associatedObject:%@",associatedObject);
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

这段代码使用AssociateObject的缓存判断kClassPropertiesKey就知道该model对象是否有进行过解析property,没有的话进行解析,同时取出model的key mapper,也同样进行缓存。
key mapper主要是用来针对某些json字段名和model数据名不一致的情况。
比如"com.app.test.name":"xxx","test_name":"xxx"这样的情况,可能对应的model数据字段名为name,那如何讲着两个值进行映射,就通过key mapper来完成。
主体的解析代码如下:

遍历成员变量列表并设置关联

-(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包涵解析与赋值时候的所有判断
            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
            //核心,通过property_getAttributes获取property的encode string,解析encode string可以解析出具体property的类型
            const char *attrs = property_getAttributes(property);//取出成员变量属性
            NSString* propertyAttributes = @(attrs);
            NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];

            //ignore read-only properties 包含 R 的为只读的,不解析
            if ([attributeItems containsObject:@"R"]) {
                continue; //to next property
            }

            //check for 64b BOOLs 检查是否是c中字符char ,char 的头是Tc
            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的scanLocation(我理解为光标)移动到T之后
            [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]) {
            //继承自NSObject的类会有 @/"  前缀,例如  "T@\"NSString<PropertyProtocol><PropertyProtocol2>\",C,N,V_type",,移动到@/"之后

                [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]
                                        intoString:&propertyType];//扫描到"<(有遵循的协议)或者"(没有遵循的协议)之后,这里的协议只指声明成员变量的时候声明遵循的协议,得到的就是class类,<>里面是遵循的协议,见上面例子

                //JMLog(@"type: %@", propertyClassName);
                p.type = NSClassFromString(propertyType);
                p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);//是否是可变的
                p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是允许的类allowedJSONTypes = @[NSString class], [NSNumber class], [NSDecimalNumber class], [NSArray class], [NSDictionary class], [NSNull class],[NSMutableString class], [NSMutableArray class], [NSMutableDictionary class]];

                //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]) {//或者是结构体,CGPoint,CGRect之类的,具体成员变量里为什么会出现这个现在还没有遇到过,官方文档说包含{的是:@property struct YorkshireTeaStruct structDefault;T{YorkshireTeaStruct="pot"i"lady"c},VstructDefault/@property YorkshireTeaStructType typedefDefault;T{YorkshireTeaStruct="pot"i"lady"c},VtypedefDefault/@property union MoneyUnion unionDefault;T(MoneyUnion="alone"f"down"d),VunionDefault
                [scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
                                    intoString:&propertyType];

                p.isStandardJSONType = NO;
                p.structName = propertyType;

            }
            //the property must be a primitive
            else {//原始 数据类型,允许的原始数据类型allowedPrimitiveTypes = @[@"BOOL", @"float", @"int", @"long", @"double", @"short",@"NSInteger", @"NSUInteger", @"Block"];
                //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];//取出基本数据类型的全称@{@"f":@"float", @"i":@"int", @"d":@"double", @"l":@"long", @"c":@"BOOL", @"s":@"short", @"q":@"long",@"I":@"NSInteger", @"Q":@"NSUInteger", @"B":@"BOOL",@"@?":@"Block"};
                //判断是否是允许的基本数据类型
                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;
            }

            NSString* customProtocol = [[self class] protocolForArrayProperty:nsPropertyName];
            if (customProtocol) {
                p.protocol = customProtocol;
            }

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

        free(properties);//注意:这里释放properties,否则会造成内存泄露
        //ascend to the super of the class
        //(will do that until it reaches the root class - JSONModel)
        //继续遍历直到基类JSONMedol
        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中看到的相呼应。
简单来说通过**class_copyPropertyList
获取属性列表,得到objc_property_t
**数组。

You can use the functions class_copyPropertyList and protocol_copyPropertyList to retrieve an array of the properties associated with a class (including loaded categories) and a protocol respectively

通过遍历**objc_property_t
调用property_getName
获得名称,property_getAttributes
**获得属性的描述(字符串)。

You can use the property_getAttributes function to discover the name and the @encode type string of a property.

存储信息。
最后
class = [class superclass]
获取父类,如果父类是JSONModel的子类,继续进行上述三步。
最后的最后
objc_setAssociatedObject

字典MatchingModel确保是KeyMapper的子集


-(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;//[self __requiredPropertyNames]会对__properties__遍历取出isOptional=NO的,并且通过objc_setAssociatedObject,这样可以只在第一次遍历,后面直接取
    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 {//这里没有懂为什么用valueForKeyPath:,没有用valueForKey:
                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
    //利用NSSet的子集 看看要求必有的是否是传入字典的子集
    if (![requiredProperties isSubsetOfSet:incomingKeys]) {

        //get a list of the missing properties
        //取出没有的成员列表变量,如果有error,赋给error,并返回NO
        [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;
}

如果得到的objc_property_t
数组是dict
的超集,说明我们的Model中有属性是赋不到值的,往往程序就会crash在这里。解决方法,属性后加入<Optional>
表示可以为空。或者
设置所有属性为可选
**。

@implementation ProductModel
+(BOOL)propertyIsOptional:(NSString*)propertyName
{ 
  return YES;
}
@end

在json数据中例如有字段名为id或者description的语言关键字,语法中式不能这么写的

@property (assign, nonatomic) int id;

只能写这种

@property (assign, nonatomic) int xxxID;

而这么写了在此func种就会由于**objc_property_t
中不包含xxxID
**crash,于是就有了keyMapper可以避免这个问题。

//赋值
-(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;//从jsonDict中以property取值
        @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;
        //判断返回的数据类型是否是允许的数据类型
        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
            //检查成员变量的set方法,property.setterType默认为kNotInspected(未检查过的),还有kNo(没有set方法),kCustom(正常的set方法,即:setPropertnameWith:),如果有赋值,并返回yes
            if ([self __customSetValue:jsonValue forProperty:property]) {
                //skip to next JSON key
                //有正常的set赋值方法,赋值,continue
                continue;
            };

            // 0) handle primitives
            //赋值基本数据类型:不是对象也不是结构体,int、float之类的
            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
            //判断是否为nil,或者NSNull类型
            if (isNull(jsonValue)) {
            //字典值为空,self之前的值非空,赋空
                if ([self valueForKey:property.name] != nil) {
                    [self setValue:nil forKey: property.name];
                }
                continue;
            }

            // 1) check if property is itself a JSONMode
            if ([self __isJSONModelSubClass:property.type]) {
                //处理jsonModel嵌套
                //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;
}

整个func主要就做赋值工作
从对象的 **properties
** 数组中,循环到 **dict
** 中取值,通过 **KVC
** 操作。循环中,使用 **keyMapper
** 的映射找对对应的字典属性的 **keyPath
** ,获取 **jsonValue
**

ps:因为是通过KVO赋值,根本是使用keyPath,所以keyMapper可以解决当model中属性名和json串中属性名不同的问题

赋值时分以下几种情况:

  1. 判断是不是空值,如果是空值并且属性非optional,error。
  2. 判断是不是合法的json类型,如果不是,error。
  3. 如果是属性:
    3.1 如果有自定义setter调用自定义setter,然后continue。
    3.2 如果没有自定setter时,属性是基本数据类型,int,double等,直接用KVC赋值。
    3.3 如果没有自定setter时,属性是一个JSONModel,就解析这个JSONModel(递归),最终还是使用KVC赋值。
    3.4 如果没有自定setter时,属性带有protocol字段,说明是个字典或者数组,将他遍历后解析。
    3.5 如果没有自定setter时,属性和json串中字段均为标准json类型,直接用KVC赋值。
    3.6 如果没有自定setter时,属性和json串中字段不同,使用JSONValueTransformer进行转换。
    3.7 处理其他case,使用KVC直接赋值。
首先在OC中拥有很多簇类。
当我们debug的时候有时会发现一个NSString在底层不是NSString,有时是NSPlaceholderString,有时又是别的。
因为NSString在设计上得时候采用了抽象工厂的设计模式,内部是一个簇类(class cluster)。
这也是NSString,NSArray,NSDictionary什么的官方不建议去继承的原因。
使用JSONValueTransformer,就是由于这些簇类。需要使用JSONValueTransformer得到真正的类型。

关于类簇以及抽象工厂模式可以看这篇文章

http://www.jianshu.com/p/0dfd1b66233a

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C C...
    GrayLand阅读 1,600评论 1 10
  • PDCA计划、行动、检查、执行,最早来自于质量管理,“高质量,不是来自基于结果的产品检验,而是来自于基于过程...
    自如得己阅读 274评论 0 0
  • 晨,舒服的躺在湿润的草地上,看太阳从地平线缓缓升起,凉风习习,光线由暗到明,穿过树影,激起心灵的涟漪,虽然静如止水...
    楚地小生阅读 535评论 0 2
  • 讲到至死不渝、忠贞不二的爱情故事,就马上想到梁山伯与祝英台,还有罗密欧与朱丽叶。 原来我以为罗密欧与朱丽叶也是像梁...
    重启的鱼阅读 579评论 0 0