JSONModel是我們要講解的重點熙参,我們首先從它的初始化方法談起析命。
-(instancetype)initWithString:(NSString*)string error:(JSONModelError**)err;
-(instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err;
-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
-(instancetype)initWithData:(NSData *)data error:(NSError **)error;
designated initializer
粗略一看,四個初始化方法工育,太可怕了虾宇。但是我們知道在iOS的設(shè)計理念里,有一種designated initializer的說法
在自己的開發(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串中屬性名不同的問題
賦值時分以下幾種情況:
- 判斷是不是空值腻扇,如果是空值并且屬性非optional,error砾嫉。
- 判斷是不是合法的json類型幼苛,如果不是,error焕刮。
- 如果是屬性:
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得到真正的類型蛹锰。