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的設(shè)計理念里,有一種designated initializer的說法


6941baebjw1eozmnjcsvaj21060ueqbx.jpg

在自己的開發(fā)過程中,合理地遵守和運用designated initializer會減少許多重復(fù)代碼如绸。
并且理解了這一個概念,對整個Cocoa框架的理解也有幫助嘱朽。 例如UIViewController的Designated initializer是

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

但是可能有人會發(fā)現(xiàn),如果你直接使用 [[viewController alloc]init]來生產(chǎn)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];//轉(zhuǎn)入重寫的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)) {//是否第一次初始化這個模型類,標(biāo)準(zhǔn)是是否綁定了kClassPropertiesKey须喂,kClassPropertiesKey取出來是數(shù)組(存放類的成員變量)
        [self __inspectProperties];//去遍歷成員變量列表吁断,并把遍歷好的放到數(shù)組中,以kClassPropertiesKey為鍵綁定:objc_setAssociatedObject坞生,
    }
    //if there's a custom key mapper, store it in the associated object
    id mapper = [[self class] keyMapper];//取出需要進行轉(zhuǎn)換的成員變量名仔役,比如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是為類或者對象增加屬性
創(chuàng)建關(guān)聯(lián)要使用到Objective-C的運行時函數(shù):objc_setAssociatedObject來把一個對象與另外一個對象進行關(guān)聯(lián)又兵。該函數(shù)需要四個參數(shù):源對象,關(guān)鍵字,關(guān)聯(lián)的對象和一個關(guān)聯(lián)策略沛厨。當(dāng)然宙地,此處的關(guān)鍵字和關(guān)聯(lián)策略是需要進一步討論的。
■ 關(guān)鍵字是一個void類型的指針逆皮。每一個關(guān)聯(lián)的關(guān)鍵字必須是唯一的宅粥。通常都是會采用靜態(tài)變量來作為關(guān)鍵字。
■ 關(guān)聯(lián)策略表明了相關(guān)的對象是通過賦值电谣,保留引用還是復(fù)制的方式進行關(guān)聯(lián)的秽梅;還有這種關(guān)聯(lián)是原子的還是非原子的。這里的關(guān)聯(lián)策略和聲明屬性時的很類似剿牺。這種關(guān)聯(lián)策略是通過使用預(yù)先定義好的常量來表示的企垦。 下面是實例代碼

//
//  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"
// 表示關(guān)聯(lián)關(guān)系的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中獲取被關(guān)聯(lián)的對象string

 // 注意晒来,這里就沒有string這個對象任何事了

 // string其實已經(jīng)變成了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數(shù)據(jù)名不一致的情況。
比如"com.app.test.name":"xxx","test_name":"xxx"這樣的情況竹习,可能對應(yīng)的model數(shù)據(jù)字段名為name,那如何講著兩個值進行映射列牺,就通過key mapper來完成整陌。
主體的解析代碼如下:

遍歷成員變量列表并設(shè)置關(guān)聯(lián)

-(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(我理解為光標(biāo))移動到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];//掃描到"<(有遵循的協(xié)議)或者"(沒有遵循的協(xié)議)之后震放,這里的協(xié)議只指聲明成員變量的時候聲明遵循的協(xié)議,得到的就是class類,<>里面是遵循的協(xié)議驼修,見上面例子

                //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]) {//掃描遵循的協(xié)議殿遂,一個協(xié)議一對<>,是否可選、忽略之類的

                    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]) {//或者是結(jié)構(gòu)體乙各,CGPoint墨礁,CGRect之類的,具體成員變量里為什么會出現(xiàn)這個現(xiàn)在還沒有遇到過耳峦,官方文檔說包含{的是:@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 {//原始 數(shù)據(jù)類型恩静,允許的原始數(shù)據(jù)類型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];//取出基本數(shù)據(jù)類型的全稱@{@"f":@"float", @"i":@"int", @"d":@"double", @"l":@"long", @"c":@"BOOL", @"s":@"short", @"q":@"long",@"I":@"NSInteger", @"Q":@"NSUInteger", @"B":@"BOOL",@"@?":@"Block"};
                //判斷是否是允許的基本數(shù)據(jù)類型
                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);

            //基本數(shù)據(jù)類型定義的時候不能遵循協(xié)議,通過類方法判斷成員變量遵循的協(xié)議
            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蹲坷,否則會造成內(nèi)存泄露
        //ascend to the super of the class
        //(will do that until it reaches the root class - JSONModel)
        //繼續(xù)遍歷直到基類JSONMedol
        class = [class superclass];
    }

    //finally store the property index in the static property index
    //將遍歷的結(jié)果綁定到類上驶乾,這樣保證只遍歷一次
    objc_setAssociatedObject(
                             self.class,
                             &kClassPropertiesKey,
                             [propertyIndex copy],
                             OBJC_ASSOCIATION_RETAIN // This is atomic
                             );
}

這么多代碼邑飒,其實總結(jié)起來就幾個步驟:
獲取當(dāng)前類的的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.

根據(jù)解析結(jié)果腔召,檢測是不是合法,如果合法創(chuàng)建對應(yīng)的JSONModelClassProperty并賦值相應(yīng)的屬性值扮惦。

然后重復(fù)執(zhí)行臀蛛,查看完當(dāng)前的類就去查詢其父類,直到?jīng)]有為止崖蜜。

最后將解析出來的property list通過Associate Object給賦值浊仆,這和我們剛剛在 setup中看到的相呼應(yīng)。
簡單來說通過**class_copyPropertyList
獲取屬性列表豫领,得到objc_property_t
**數(shù)組抡柿。

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
調(diào)用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的子類课蔬,繼續(xù)進行上述三步囱稽。
最后的最后
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
    //處理有需要轉(zhuǎn)換名字的成員屬性
    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
數(shù)組是dict
的超集铺遂,說明我們的Model中有屬性是賦不到值的衫哥,往往程序就會crash在這里。解決方法襟锐,屬性后加入<Optional>
表示可以為空撤逢。或者
設(shè)置所有屬性為可選
**。

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

在json數(shù)據(jù)中例如有字段名為id或者description的語言關(guān)鍵字蚊荣,語法中式不能這么寫的

@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
        //取出數(shù)據(jù)為空
        if (isNull(jsonValue)) {
            //skip this property, continue with next property
            //如果是可選的繼續(xù)互例,
            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;
        //判斷返回的數(shù)據(jù)類型是否是允許的數(shù)據(jù)類型
        for (Class allowedType in allowedJSONTypes) {
            if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
                isValueOfAllowedType = YES;
                break;
            }
        }
        //不是允許的數(shù)據(jù)類型奢入,報錯
        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默認(rèn)為kNotInspected(未檢查過的),還有kNo(沒有set方法)媳叨,kCustom(正常的set方法腥光,即:setPropertnameWith:),如果有賦值,并返回yes
            if ([self __customSetValue:jsonValue forProperty:property]) {
                //skip to next JSON key
                //有正常的set賦值方法糊秆,賦值武福,continue
                continue;
            };

            // 0) handle primitives
            //賦值基本數(shù)據(jù)類型:不是對象也不是結(jié)構(gòu)體,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
** 數(shù)組中,循環(huán)到 **dict
** 中取值昂芜,通過 **KVC
** 操作莹规。循環(huán)中,使用 **keyMapper
** 的映射找對對應(yīng)的字典屬性的 **keyPath
** 泌神,獲取 **jsonValue
**

ps:因為是通過KVO賦值访惜,根本是使用keyPath,所以keyMapper可以解決當(dāng)model中屬性名和json串中屬性名不同的問題

賦值時分以下幾種情況:

  1. 判斷是不是空值腻扇,如果是空值并且屬性非optional,error砾嫉。
  2. 判斷是不是合法的json類型幼苛,如果不是,error焕刮。
  3. 如果是屬性:
    3.1 如果有自定義setter調(diào)用自定義setter舶沿,然后continue。
    3.2 如果沒有自定setter時配并,屬性是基本數(shù)據(jù)類型括荡,int,double等溉旋,直接用KVC賦值畸冲。
    3.3 如果沒有自定setter時,屬性是一個JSONModel,就解析這個JSONModel(遞歸)邑闲,最終還是使用KVC賦值算行。
    3.4 如果沒有自定setter時,屬性帶有protocol字段苫耸,說明是個字典或者數(shù)組州邢,將他遍歷后解析。
    3.5 如果沒有自定setter時褪子,屬性和json串中字段均為標(biāo)準(zhǔn)json類型量淌,直接用KVC賦值。
    3.6 如果沒有自定setter時嫌褪,屬性和json串中字段不同呀枢,使用JSONValueTransformer進行轉(zhuǎn)換。
    3.7 處理其他case渔扎,使用KVC直接賦值硫狞。
首先在OC中擁有很多簇類。
當(dāng)我們debug的時候有時會發(fā)現(xiàn)一個NSString在底層不是NSString晃痴,有時是NSPlaceholderString残吩,有時又是別的。
因為NSString在設(shè)計上得時候采用了抽象工廠的設(shè)計模式倘核,內(nèi)部是一個簇類(class cluster)泣侮。
這也是NSString,NSArray紧唱,NSDictionary什么的官方不建議去繼承的原因活尊。
使用JSONValueTransformer,就是由于這些簇類漏益。需要使用JSONValueTransformer得到真正的類型蛹锰。

關(guān)于類簇以及抽象工廠模式可以看這篇文章

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绰疤,隨后出現(xiàn)的幾起案子铜犬,更是在濱河造成了極大的恐慌,老刑警劉巖轻庆,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癣猾,死亡現(xiàn)場離奇詭異,居然都是意外死亡余爆,警方通過查閱死者的電腦和手機纷宇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛾方,“玉大人像捶,你說我怎么就攤上這事上陕。” “怎么了作岖?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵唆垃,是天一觀的道長。 經(jīng)常有香客問我痘儡,道長辕万,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任沉删,我火速辦了婚禮渐尿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矾瑰。我一直安慰自己砖茸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布殴穴。 她就那樣靜靜地躺著凉夯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪采幌。 梳的紋絲不亂的頭發(fā)上劲够,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音休傍,去河邊找鬼征绎。 笑死,一個胖子當(dāng)著我的面吹牛磨取,可吹牛的內(nèi)容都是我干的人柿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼忙厌,長吁一口氣:“原來是場噩夢啊……” “哼凫岖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逢净,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤隘截,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后汹胃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡东臀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年着饥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惰赋。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡宰掉,死狀恐怖呵哨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情轨奄,我是刑警寧澤孟害,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站挪拟,受9級特大地震影響挨务,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玉组,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一谎柄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惯雳,春花似錦朝巫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至潮孽,卻和暖如春揪荣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恩商。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工变逃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠堪。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓贝奇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忙芒。 傳聞我的和親對象是個殘疾皇子膀哲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 禪與 Objective-C 編程藝術(shù) (Zen and the Art of the Objective-C C...
    GrayLand閱讀 1,623評論 1 10
  • PDCA計劃、行動陌粹、檢查撒犀、執(zhí)行,最早來自于質(zhì)量管理掏秩,“高質(zhì)量或舞,不是來自基于結(jié)果的產(chǎn)品檢驗,而是來自于基于過程...
    自如得己閱讀 276評論 0 0
  • 晨蒙幻,舒服的躺在濕潤的草地上映凳,看太陽從地平線緩緩升起,涼風(fēng)習(xí)習(xí)邮破,光線由暗到明诈豌,穿過樹影仆救,激起心靈的漣漪,雖然靜如止水...
    楚地小生閱讀 540評論 0 2
  • 講到至死不渝矫渔、忠貞不二的愛情故事彤蔽,就馬上想到梁山伯與祝英臺,還有羅密歐與朱麗葉庙洼。 原來我以為羅密歐與朱麗葉也是像梁...
    重啟的魚閱讀 586評論 0 0