JSONModel源代碼解析

前言

本文的demo更新在github上。

客戶端請求服務器看蚜,經(jīng)常使用的時JSON方式傳遞數(shù)據(jù)除秀。一些第三方開源庫幫助我們將JSON轉化為Model對象,其中比較有名的有:YYModel,JSONModel,Mantle,MJExtension等栗弟。今天主要講一下JSONModel和相應的源代碼。 (以下代碼都是建立在release 1.20版本的基礎上工闺。)

常規(guī)解析

解析JSON數(shù)據(jù)的最基礎的方法是使用NSJSONSerialization乍赫,比如下面的一個最簡單的網(wǎng)絡請求

    NSData* ghData = [NSData dataWithContentsOfURL: [NSURL URLWithString:@"http://xxxx"]];
    NSDictionary* json = nil;
    if (ghData) {
        json = [NSJSONSerialization
                JSONObjectWithData:ghData
                options:kNilOptions
                error:nil];
    }

最后通過NSJSONSerialization去將數(shù)據(jù)解析成了一個dictionary

如果有這樣一組json數(shù)據(jù):

{
"number":"13612345678", 
"name":"Germany",
 "age": 49
}

那我們會去建立相應的Object對象

@interface TestObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *number;
@property (nonatomic, assign) NSInteger age;
@end

然后進行設置

    TestObject *testObject = [[TestObject alloc]init];
    testObject.name     = json[@"name"];
    testObject.number   = json[@"number"];
    testObject.age      = [json[@"age"] integerValue];

這么做雖然正確,但如果所有數(shù)據(jù)都這么處理陆蟆,會有一些麻煩:

  • 1.你需要很小心的處理model property類型與dictionary中的數(shù)據(jù)對應類型
    比如有一個NSURL *url的值雷厂,你需要在json[@"url"]這個NSString *類型進行一次轉化成NSURL *,但編譯器并不會提示你這樣的錯誤叠殷,很多時候你如果忘記了就會犯錯
  • 2.如果你的賦值地點過于的多改鲫,你每一次修改model的property,就需要把所有賦值地方進行一次整體的更改,會比較麻煩
  • 3.很多時候json數(shù)據(jù)如果有遺漏或者變化像棘,比較難發(fā)現(xiàn)
    比如對應上面的age這個值纫塌,json數(shù)據(jù)中如果不包含age,通過[json[@"age"] integerValue]的寫法讲弄,就會把值設置為0措左,這在很多時候容易被忽略,以為json數(shù)據(jù)中包含這樣的值避除。

JSONModel解析

我們只需要建立這樣一個JSONModel對象

#import <JSONModel/JSONModel.h>

@interface TestJSONModel : JSONModel
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *number;
@property (nonatomic, assign) NSInteger age;
@end

并調(diào)用

    JSONModelError *error = nil;
    TestJSONModel *testJSONModel = [[TestJSONModel alloc]initWithDictionary:json error:&error];

就可以將model的值進行自行設置怎披,相對于常規(guī)方法,大大簡化了代碼量和難度瓶摆。

JSONModel源代碼分析

目錄結構

我們先來看一下JSONModel的目錄結構

JSONModel目錄

可以看到凉逛,項目中其實還包括networking,transformer等有關的類,但我們這次解析主要聚焦在JSONModel.m上群井,也不是逐行解析状飞,主要講正題的思路和方法。

核心代碼

初始化代碼可以說是核心代碼书斜,代碼如下:

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

    //invalid input, just create empty instance
    //2.類型判斷
    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
    //3.核心诬辈,初始化映射property
    self = [self init];
    if (!self) {
        
        //super init didn't succeed
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }
    
    //check incoming data structure
    //4.檢查映射結構是否能夠從dictionary中找到相應的數(shù)據(jù)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }
    
    //import the data from a dictionary
    //5.進行數(shù)據(jù)賦值
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }
    
    //run any custom model validation
    //6.本地數(shù)據(jù)檢查
    if (![self validate:err]) {
        return nil;
    }
    
    //model is valid! yay!
    return self;
}

主要分為以下6塊:

  • 1.空值判斷
  • 2.輸入類型dictionary判斷
  • 3.初始化:解析model對象,并且映射property
  • 4.查值:檢查model property名與數(shù)據(jù)來源json字典中數(shù)據(jù)名荐吉,判斷是否所有property都有值
  • 5.賦值:進行賦值
  • 6.本地數(shù)據(jù)正確性檢查
    以下我將主要解析3焙糟,4,5這三部分的主代碼

初始化

以下是初始化的調(diào)用函數(shù)

-(void)__setup__
{
    //if first instance of this model, generate the property list
    //使用AssociateObject進行映射property的緩存样屠,判斷是否映射過
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
        [self __inspectProperties];
    }

    //if there's a custom key mapper, store it in the associated object
    //獲取對象的keyMapper影射穿撮,同樣使用AssociateObject進行映射property的緩存
    id mapper = [[self class] keyMapper];
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );
    }
}

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

這段代碼使用AssociateObject的緩存判斷kClassPropertiesKey就知道該model對象是否有進行過解析property,沒有的話進行解析痪欲,同時取出model的key mapper悦穿,也同樣進行緩存。
key mapper主要是用來針對某些json字段名和model數(shù)據(jù)名不一致的情況业踢。
比如"com.app.test.name":"xxx","test_name":"xxx"這樣的情況栗柒,可能對應的model數(shù)據(jù)字段名為name,那如何講著兩個值進行映射陨亡,就通過key mapper來完成傍衡。
主體的解析代碼如下:

//inspects the class, get's a list of the class properties
//解析property結構主體
-(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;
        //賦值所有property列表深员,進行循環(huán)判斷
        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
            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";
            }
            
            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
            //解析一個類倦畅,包括自己創(chuàng)建的類和oc自帶類NSString等
            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
                //解析protocol的string
                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
            //解析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
            //其他類型都是基本類型遮糖,比如int float等
            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];
                }

            }

            NSString *nsPropertyName = @(propertyName);
            //本地覆蓋方法去判斷是不是Optional
            if([[self class] propertyIsOptional:nsPropertyName]){
                p.isOptional = YES;
            }
            
            if([[self class] propertyIsIgnored:nsPropertyName]){
                p = nil;
            }
            //本地覆蓋方法去判斷是不是有protocol
            NSString* customProtocol = [[self class] protocolForArrayProperty:nsPropertyName];
            if (customProtocol) {
                p.protocol = customProtocol;
            }
            
            //few cases where JSONModel will ignore properties automatically
            if ([propertyType isEqualToString:@"Block"]) {
                p = nil;
            }
            
            //add the property object to the temp index
            //通過kvc去設置相應的值
            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
    //使用AssociateObject進行緩存
    objc_setAssociatedObject(
                             self.class,
                             &kClassPropertiesKey,
                             [propertyIndex copy],
                             OBJC_ASSOCIATION_RETAIN // This is atomic
                             );
}

看上去比較長,其實我們只需要明白以下幾個概念就可以比較容易理解:

  • 1.runtime

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

  • 2.objc_property_t *properties = class_copyPropertyList(Class cls, unsigned int *count);

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

  • 3.const char *property_getAttributes(objc_property_t property)

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

以上幾個概念都可以從Objective-C Runtime Programming Guide上找到更加具體的解釋叠赐,尤其是對應encode string每一個字符的含義欲账。

**簡單來說就是:
使用runtime的class_copyPropertyList方法去獲得所有model對象的property列表屡江,再使用
property_getAttributes獲得property的encode string,通過解析encode string去獲得property對象的正確含義赛不。
在解析的過程中惩嘉,使用NSScanner去掃描encode string,并使用JSONModelClassProperty的結構體去保存相關信息。
其中對于protocol的使用較為特殊踢故,在這里的protocol并非我們平常當作接口抽象的作用文黎,而是單純的為了讓encode string中增加相應的字段,可以在解析與賦值的時候給予特定的含義殿较。
**

舉個解析的例子:
這個是JSONModel自帶demo中的一個結構體耸峭,可以看到他的相關property

@protocol LoanModel @end

@interface LoanModel : JSONModel

@property (strong, nonatomic) NSString* name;
@property (strong, nonatomic) NSString* status;
@property (strong, nonatomic) NSString* use;

@property (strong, nonatomic) LocationModel* location;

@end

當解析到最后一行的property@property (strong, nonatomic) LocationModel* location;我設置了一個斷點,查看結果

propertyAttributes
JSONModelClassProperty

可以看到淋纲,對于location來說劳闹,它的類為"與"中的LocationModel,并且它還是&(retain),N(nonatomic)的洽瞬。
而protocol則會在encode string的<>中本涕,JSONModel通過這樣的方式,可以讓我們快速設置一個property的一些屬性伙窃,比如

@interface KivaFeed : JSONModel

@property (strong, nonatomic) NSArray<LoanModel, ConvertOnDemand>* loans;

@end

中的loans偏友,它不僅代表著loans這個array中包含的元素為LoanModel,而且它還有JSONModel特別設置的幾個特性ConvertOnDemand(懶加載),這些特性包括

  • 可選擇:isOptional
  • 懶加載:convertsOnDemand
  • 索引key:isIndex

通過protocol就可以達到標明array與dictionary中對應元素的類型对供,和一些對于property解析的時候有用的表示位他。

查值

-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
    //check if all required properties are present
    //將輸入dictionary的keys裝入set,將映射的property的keys裝入set
    NSArray* incomingKeysArray = [dict allKeys];
    NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
    
    //transform the key names, if necessary
    //如果存在keyMapper映射产场,在對應set中找到相應key進行替換
    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;
            
            //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
    //判斷property解析的set是不是dictionary所有key的子set來判斷是否全部包含
    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;
}

查值的作用主要就是為了能夠檢查是否model的所有property是否都能夠被賦值鹅髓,如果不能則說明缺少值則拋出錯誤。這邊主要的亮點就是使用了NSSet京景,將dictionary的所有key存入一個set:incomingKeys窿冯,并且將key mapper映射名進行替換。將剛解析出來的model所有property的name也存入一個set:requiredProperties确徙,判斷兩者是不是包含關系醒串。

賦值

-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
    //loop over the incoming keys and set self's properties
    //循環(huán)遍歷映射出來的JSONModelClassProperty結構體
    for (JSONModelClassProperty* property in [self __properties__]) {
        
        //convert key name ot model keys, if a mapper is provided
        //keyMapper映射,獲取鎮(zhèn)真正的值
        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 (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ù)輸入類型是不是允許的json類型
        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方法進行set
            if ([self __customSetValue:jsonValue forProperty:property]) {
                //skip to next JSON key
                continue;
            };
            
            // 0) handle primitives
            // 代表基礎類型鄙皇,比如int float等芜赌,直接使用kvc賦值
            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結構,進行遞歸遍歷伴逸,先將子結構遍歷完并賦值完成
            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的字段缠沈,該字段主要用來表明array或者dictionary中的對象類型
                if (property.protocol) {
                    
                    //JMLog(@"proto: %@", p.protocol);
                    //循環(huán)遍歷子內(nèi)容,將對應的類型賦給相應的array或者dictionary
                    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類型,比如nsstring等
                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轉換為nsurl等
                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)
                    // 獲取真實的json數(shù)據(jù)類型
                    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
                    // 通過property類型和json數(shù)據(jù)類型進行轉換的判斷
                    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
                        // 通過 JSONValueTransformer 進行類型轉換
                        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)
                    if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                        [self setValue:jsonValue forKey: property.name];
                    }
                }
            }
        }
    }
    
    return YES;
}

代碼看上去很長,其實也比較好理解:
循環(huán)遍歷model的每一個解析出來的property結構柬赐,首先從dictioanry拿出真正對應property的value亡问,進行value一系列的值判斷。value可用的情況下肛宋,就開始進行賦值玛界,有setter方法的通過setter方法賦值,基礎類型int悼吱,float等直接賦值慎框,如果property又是一個JSONModel,就遞歸先將子Model進行整體解析后添。如果包含protocol字段笨枯,則表明內(nèi)部是一個array或者dictionary,并包含這個protocol字段的對象解析遇西。對于其他情況馅精,應該是一種類型的轉換,通過獲取值類型和property類型粱檀,調(diào)用相應的轉換方法進行賦值洲敢。
其中值得一提的就是JSONValueTransformer的類型轉化,它解決了我們之前所說的麻煩1茄蚯,將數(shù)據(jù)類型得以正確轉換压彭。

總結

至此,JSONModel主代碼的作為渗常,基本解釋的差不多了壮不。
總的來說JSONModel的源代碼有以下優(yōu)點:

  • Runtime動態(tài)解析model數(shù)據(jù)類型
  • AssociatedObject緩存
  • keyMapper映射
  • NSScanner掃描String
  • JSONValueTransformer類型轉換
  • KVC附值
  • 。皱碘。询一。

如果以上有任何我說錯的地方,或者可以解釋的更好的地方癌椿,也歡迎給我留言健蕊,我也會修改我的錯誤。Thanks踢俄。
(PS:和同事聊起這方面的話題缩功,他表示YYModel的效率會比JSONModel高好幾倍,下一份就看一下YYModel的源代碼褪贵。)

參考資料

本文csdn地址
1.Objective-C Runtime Programming Guide
2.JSONModel源碼解析

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掂之,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脆丁,更是在濱河造成了極大的恐慌世舰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件槽卫,死亡現(xiàn)場離奇詭異跟压,居然都是意外死亡,警方通過查閱死者的電腦和手機歼培,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門震蒋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躲庄,你說我怎么就攤上這事查剖。” “怎么了噪窘?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵笋庄,是天一觀的道長。 經(jīng)常有香客問我倔监,道長直砂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任浩习,我火速辦了婚禮静暂,結果婚禮上,老公的妹妹穿的比我還像新娘谱秽。我一直安慰自己洽蛀,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布疟赊。 她就那樣靜靜地躺著辱士,像睡著了一般。 火紅的嫁衣襯著肌膚如雪听绳。 梳的紋絲不亂的頭發(fā)上颂碘,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音椅挣,去河邊找鬼头岔。 笑死,一個胖子當著我的面吹牛鼠证,可吹牛的內(nèi)容都是我干的峡竣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼量九,長吁一口氣:“原來是場噩夢啊……” “哼适掰!你這毒婦竟也來了颂碧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤类浪,失蹤者是張志新(化名)和其女友劉穎载城,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體费就,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡诉瓦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了力细。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睬澡。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖眠蚂,靈堂內(nèi)的尸體忽然破棺而出煞聪,到底是詐尸還是另有隱情,我是刑警寧澤逝慧,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布米绕,位于F島的核電站,受9級特大地震影響馋艺,放射性物質發(fā)生泄漏栅干。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一捐祠、第九天 我趴在偏房一處隱蔽的房頂上張望碱鳞。 院中可真熱鬧,春花似錦踱蛀、人聲如沸窿给。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崩泡。三九已至,卻和暖如春猬膨,著一層夾襖步出監(jiān)牢的瞬間角撞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工勃痴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谒所,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓沛申,卻偏偏與公主長得像劣领,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铁材,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

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