JSONModel的使用以及源码解析

前言


如今基本都是客户端请求服务器的模式,数据传递方式基本都使用JSON的字符串方式。然而当我们获取了服务器发来的字符串时,需要将其转换成我们的数据模型。由于手动转换代码的繁琐,就有了一些自动化转换的第三方开源库。其中比较有名的有YYModelJSONModelMantleMJExtension。今天主要说明的是JSONModel的源码。

上述均说明一点:懒人永远都是社会进步的核心动力。。。

JSONModel的使用


我们从服务器请求到了如下的JSON数据:

{@"name":@"xxx",@"age":@"xxx",@"sex":@"xxx"}

我们只需要建立如下Model类:

#import "JSONModel.h"

@interface MyModel : JSONModel

@property (strong, nonatomic) NSString * name;
@property (strong, nonatomic) NSString * age;
@property (strong, nonatomic) NSString * sex;

@end

随后进行如下操作:

JSONModelError *error = nil;
MyModel *myModel =  [[MyModel alloc] initWithString:jsonString error:&error];

JSONModel会自动将jsonString转换成我们所需要的数据模型。此外还有其他方法以便使用:

- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err;
- (instancetype)initWithData:(NSData *)data error:(NSError **)error;
- (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err;
- (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err;

上述初始化方法都大相径庭,如果上述方法传入的参数均合法,那么所定义的所有属性都会与该JSON值相匹配,同时JSONModel会转成所需要的数据类型。(通过JSONValueTransformer实现,有兴趣的可以去看下源码)

下面举出自动转换映射例子:

1.命名自动匹配

{
  "id": "123",
  "name": "Product name",
  "price": 12.95
}
@interface ProductModel : JSONModel
@property (assign, nonatomic) int id;
@property (strong, nonatomic) NSString* name;
@property (assign, nonatomic) float price;
@end

@implementation ProductModel
@end

2.model中包含其他model

{
  "order_id": 104,
  "total_price": 13.45,
  "product" : {
    "id": "123",
    "name": "Product name",
    "price": 12.95
  }
}
@interface OrderModel : JSONModel
@property (assign, nonatomic) int order_id;
@property (assign, nonatomic) float total_price;
@property (strong, nonatomic) ProductModel* product;
@end

@implementation OrderModel
@end

3.model中包含其他model的集合

{
"order_id": 104,
"total_price": 103.45,
"products" : [
  {
    "id": "123",
    "name": "Product #1",
    "price": 12.95
  },
  {
    "id": "137",
    "name": "Product #2",
    "price": 82.95
  }
]
}
@protocol ProductModel
@end

@interface ProductModel : JSONModel

@property (assign, nonatomic) int id;
@property (strong, nonatomic) NSString* name;
@property (assign, nonatomic) float price;

@end

@implementation ProductModel
@end

@interface OrderModel : JSONModel

@property (assign, nonatomic) int order_id;
@property (assign, nonatomic) float total_price;
@property (strong, nonatomic) NSArray<ProductModel>* products;

@end

@implementation OrderModel
@end

4.键值对映射,此可以解决JSON串中含有系统关键字(如id,description等),需使用键值映射转换(JSONKeyMapper)。

{
"id": 104,
"description" : [
  {
    "name": "Product#1",
    "price": {
      "usd": 12.95
    }
  }
]
}
@interface OrderModel : JSONModel

@property (assign, nonatomic) int order_id;
@property (assign, nonatomic) float price;
@property (strong, nonatomic) NSString* productName;

@end
@implementation OrderModel

+(JSONKeyMapper*)keyMapper
{

  return [[JSONKeyMapper alloc] initWithDictionary:@{
    @"id": @"order_id",
    @"description.name": @"productName",
    @"description.price.usd": @"price"
  }];

}

@end

5.将JSON串中带下划线的转换成首字母大写
<pre>
+(<em><strong>JSONKeyMapper</strong> *)keyMapper {
return [<em><strong>JSONKeyMapper</strong> mapperFromUnderscoreCaseToCamelCase];
}
</pre>

6.二级转换

{
"order_id": 104,
"total_price": 103.45,
"products" : [
  {
    "productsId": "123",
    "name": "Product #1",
    "price": 12.95
  },
  {
    "productsId": "137",
    "name": "Product #2",
    "price": 82.95
  }
]
}
@protocol ProductModel
@end

@interface ProductModel : JSONModel
@property (assign, nonatomic) int productsId;
@property (strong, nonatomic) NSString* name;
@property (assign, nonatomic) float price;

@end

@implementation ProductModel
@end

@interface OrderModel : JSONModel

@property (assign, nonatomic) int order_id;
@property (assign, nonatomic) float total_price;
@property (strong, nonatomic) NSArray<ProductModel, ConvertOnDemand>* products;

@end

@implementation OrderModel
@end

<pre>ps:
ConvertOnDemand:懒加载
Optional:可为空
Ignored:可忽视此属性
</pre>

JSONModel的源码解析


NSError *error = nil;
MyJSONModel *model = [[MyJSONModel alloc] initWithDictionary:dict error:&error];

以上是数据转换的代码,一句话就可以实现字典转模型的操作,我们来看下源码。

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

    //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;
    }

    //create a class instance
    self = [self init];
    if (!self) {

        //super init didn't succeed
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }

    //check incoming data structure
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

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

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

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

上述代码分为以下几个部分

  1. 判断传入字典是否为空,为空返回nil
  2. 判断传入参数是否为字典,非字典返回nil
  3. 初始化JSONModel模型对象,初始化失败返回nil
    3.1 使用<strong>runtime</strong>的<strong>objc_getAssociatedObject</strong>判断<strong>model</strong>的<strong>kClassPropertiesKey</strong>是否映射过。
    3.2 若未映射调用<strong>[self __inspectProperties];</strong>进行对<strong>kClassPropertiesKey</strong>参数的映射。
    3.3 与前者类似映射<strong>kMapperObjectKey</strong>
static const char * kMapperObjectKey;
static const char * kClassPropertiesKey;

-(id)init {
    self = [super init];
    if (self) {
        [self __setup__];
    }
    return self;
}

-(void)__setup__  {
    //3.1 所做的事
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
        //3.2 所做的事
        [self __inspectProperties];
    }
    //3.3 所做的事
    id mapper = [[self class] keyMapper];
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );
    }
}

#pragma mark - key mapping
+(JSONKeyMapper*)keyMapper
{
    return nil;
}
-(void)__inspectProperties {
    NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
    Class class = [self class];
    NSScanner* scanner = nil;
    NSString* propertyType = nil;
    //
    while (class != [JSONModel class]) { 
        ......
        class = [class superclass];
    }
    //3.2 映射,连接二者的关系
    objc_setAssociatedObject(
                             self.class,
                             &kClassPropertiesKey,
                             [propertyIndex copy],
                             OBJC_ASSOCIATION_RETAIN 
                             );
}

4.调用 <strong>[self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]</strong>确保<strong>dict</strong>是<strong>keyMapper</strong>的子集。
5.调用<strong>[self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]</strong>来查值。然后将解析的值赋给<strong>Model</strong>的<strong>property</strong>。
6.调用<strong>[self validate:err]</strong>,用户自定义重写<strong>-(BOOL)validate:(NSError**)error</strong>验证。

初始化JSONModel模型对象

<strong>[self init]</strong> 调用 <strong>[self __setup__]</strong> 调用 <strong>[self __inspectProperties]</strong>。

  1. <strong>[self __setup__]</strong> 所做的就是建立映射关系表,存放在全局变量中。这里用到了<strong>runtime</strong>的<strong>objc_getAssociatedObject</strong>,动态的给self这个模型类添加了一个属性。
  2. <strong>[self __inspectProperties]</strong> 为具体实现。
-(void)__inspectProperties {
    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]) {

        unsigned int propertyCount; 
        //通过class_copyPropertyList获取属性列表,得到objc_property_t数组
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

        //loop over the class properties
        //循环遍历objc_property_t数组
        for (unsigned int i = 0; i < propertyCount; i++) {

            JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

            //获取属性property的名字
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            p.name = @(propertyName);

            //获取属性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 converters
                p.structName = @"BOOL";
            }
            
            //使用NSScanner扫描之前获得的属性描述(字符串)
            scanner = [NSScanner scannerWithString: propertyAttributes];

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

                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"]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
                        p.isIndex = YES;
#pragma GCC diagnostic pop

                        objc_setAssociatedObject(
                                                 self.class,
                                                 &kIndexPropertyNameKey,
                                                 p.name,
                                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                                 );
                    } else if([protocolName isEqualToString:@"Ignore"]) {
                        p = nil;
                    } else {
                        p.protocol = protocolName;
                    }

                    [scanner scanString:@">" intoString:NULL];
                }

            }
  
            //解析结构体
            //check if the property is a structure
            else if ([scanner scanString:@"{" intoString: &propertyType]) {
                [scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
                                    intoString:&propertyType];

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

            }

            //其余的基本数据类型,int等
            //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 mistaken -> exception
                    @throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"
                                                   reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]
                                                 userInfo:nil];
                }

            }

            //判断是否为optional或者ignored
            NSString *nsPropertyName = @(propertyName);
            if([[self class] propertyIsOptional:nsPropertyName]){
                p.isOptional = YES;
            }

            if([[self class] propertyIsIgnored:nsPropertyName]){
                p = nil;
            }
            
            //判断是不是有protocol
            Class customClass = [[self class] classForCollectionProperty:nsPropertyName];
            if (customClass) {
                p.protocol = NSStringFromClass(customClass);
            }

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

简单来说
通过<strong>class_copyPropertyList</strong>获取属性列表,得到<strong>objc_property_t</strong>数组。

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

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

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

在通过NSScanner扫描属性的描述(@encode,关于描述请戳我这里跳转官方文档),再使用<strong>JSONModelClassProperty</strong>存储信息。

最后<strong>class = [class superclass]</strong>获取父类,如果父类是JSONModel的子类,继续进行上述三步。

最后的最后<strong>objc_setAssociatedObject</strong>。

字典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;
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];

    //transform the key names, if necessary
    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] : property.name;

            //check 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;
}

整个func就做了两件事:

  1. 得到传入的json数据<strong>dict</strong>。
  2. <strong>dict</strong>和之前得到的<strong>objc_property_t</strong>数组对比Matching 。

需要注意的有:

  1. 如果得到的<strong>objc_property_t</strong>数组是<strong>dict</strong>的超集,说明我们的Model中有属性是赋不到值的,往往程序就会crash在这里。
    解决方法,属性后加入<strong><Optional></strong>表示可以为空。或者<strong><em>设置所有属性为可选</strong>。
@implementation ProductModel
+(BOOL)propertyIsOptional:(NSString*)propertyName  {
    return YES;
}
@end
  1. 在json数据中例如有字段名为id或者description的语言关键字,语法中式不能这么写的
@property (assign, nonatomic) int id;

只能写这种

@property (assign, nonatomic) int xxxID;

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

{
"id": 104,
"description" : [
    {
        "name": "Product#1",
        "price": {
            "usd": 12.95
        }
    }
]}
@interface OrderModel : JSONModel
@property (assign, nonatomic) int order_id;
@property (assign, nonatomic) float price;
@property (strong, nonatomic) NSString* productName;
@end
@implementation OrderModel
+(JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:@{
      @"id": @"order_id",
      @"description.name": @"productName",
      @"description.price.usd": @"price"
    }];
}
@end

赋值给Model

-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
    //loop over the incoming keys and set self's properties
    //从对象的properties数组中,循环到dict中取值
    //通过KVC操作
    for (JSONModelClassProperty* property in [self __properties__]) {

        //convert key name to model keys, if a mapper is provided
        //使用keyMapper的映射找对对应的字典属性的keyPath,获取jsonValue
        NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;

        //general check for data type compliance
        id jsonValue;
        @try {
            jsonValue = [dict valueForKeyPath: jsonKeyPath];
        }
        @catch (NSException *exception) {
            jsonValue = dict[jsonKeyPath];
        }

        //check for Optional properties
        if (isNull(jsonValue)) {
            //skip this property, continue with next property
            if (property.isOptional || !validation) continue;

            if (err) {
                //null value for required property
                NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];
                JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
                *err = [dataErr errorByPrependingKeyPathComponent:property.name];
            }
            return NO;
        }

        Class jsonValueClass = [jsonValue class];
        BOOL isValueOfAllowedType = NO;

        for (Class allowedType in allowedJSONTypes) {
            if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
                isValueOfAllowedType = YES;
                break;
            }
        }

        if (isValueOfAllowedType==NO) {
            //type not allowed
            JMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));

            if (err) {
                NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];
                JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
                *err = [dataErr errorByPrependingKeyPathComponent:property.name];
            }
            return NO;
        }

        //check if there's matching property in the model
        if (property) {

            // check for custom setter, than the model doesn't need to do any guessing
            // how to read the property's value from JSON
            //setter方法进行赋值
            if ([self __customSetValue:jsonValue forProperty:property]) {
                //skip to next JSON key
                continue;
            };

            // 0) handle primitives
            // 基础类型,int,double等,直接使用keyPath赋值
            if (property.type == nil && property.structName==nil) {

                //generic setter
                if (jsonValue != [self valueForKey:property.name]) {
                    [self setValue:jsonValue forKey: property.name];
                }

                //skip directly to the next key
                continue;
            }

            // 0.5) handle nils
            if (isNull(jsonValue)) {
                if ([self valueForKey:property.name] != nil) {
                    [self setValue:nil forKey: property.name];
                }
                continue;
            }


            // 1) check if property is itself a JSONModel
            //判断属性是否是JSONModel,如果是进行遍历之后根据keyPath赋值
            if ([self __isJSONModelSubClass:property.type]) {

                //initialize the property's model, store it
                JSONModelError* initErr = nil;
                id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];

                if (!value) {
                    //skip this property, continue with next property
                    if (property.isOptional || !validation) continue;

                    // Propagate the error, including the property name as the key-path component
                    if((err != nil) && (initErr != nil))
                    {
                        *err = [initErr errorByPrependingKeyPathComponent:property.name];
                    }
                    return NO;
                }
                if (![value isEqual:[self valueForKey:property.name]]) {
                    [self setValue:value forKey: property.name];
                }

                //for clarity, does the same without continue
                continue;

            } else {

                // 2) check if there's a protocol to the property
                //  ) might or not be the case there's a built in transform for it
                //判断是否包含protocol,用来表示字典或者数组中的数据类型,其中遍历后赋值
                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
                //处理匹配标准的json类型
                if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {

                    //mutable properties
                    if (property.isMutable) {
                        jsonValue = [jsonValue mutableCopy];
                    }

                    //set the property value
                    if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                        [self setValue:jsonValue forKey: property.name];
                    }
                    continue;
                }

                // 3.3) handle values to transform
                //处理值的转换,如NSString转成NSNumber
                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)
                    //通过JSONValueTransformer转换
                    Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];

                    //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 mistake -> exception
                        @throw [NSException exceptionWithName:@"Type not allowed"
                                                       reason:[NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name]
                                                     userInfo:nil];
                        return NO;
                    }

                } else {
                    // 3.4) handle "all other" cases (if any)
                    //处理其他case
                    if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                        [self setValue:jsonValue forKey: property.name];
                    }
                }
            }
        }
    }

    return YES;
}

整个func主要就做赋值工作

从对象的 <strong>properties</strong> 数组中,循环到 <strong>dict</strong> 中取值,通过 <strong>KVC</strong> 操作。
循环中,使用 <strong>keyMapper</strong> 的映射找对对应的字典属性的 <strong>keyPath</strong> ,获取 <strong>jsonValue</strong>
<pre>ps:因为是通过<strong>KVO</strong>赋值,根本是使用<strong>keyPath</strong>,所以<strong>keyMapper</strong>可以解决当<strong>model</strong>中属性名和<strong>json</strong>串中属性名不同的问题</pre>
赋值时分以下几种情况:

  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直接赋值。
    <pre>首先在OC中拥有很多簇类。
    当我们debug的时候有时会发现一个NSString在底层不是NSString,有时是NSPlaceholderString,有时又是别的。
    因为NSString在设计上得时候采用了抽象工厂的设计模式,内部是一个簇类(class cluster)。
    这也是NSString,NSArray,NSDictionary什么的官方不建议去继承的原因。
    使用JSONValueTransformer,就是由于这些簇类。需要使用JSONValueTransformer得到真正的类型,然后找到最适合的转换方法。
    </pre>

总结


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

推荐阅读更多精彩内容