讀完這篇文章你可以自己寫一個(gè) YYModel 這樣的神器叹括,這篇文章類似一個(gè)源碼解析枯芬,但不同的是,它不光光是解析碰辅,更是實(shí)戰(zhàn)懂昂,因?yàn)槲矣X得學(xué)習(xí)一個(gè)東西必須要自己寫一遍才算是真的學(xué)了一遍,否則即便是讀完了源碼印象還是不會(huì)太深刻没宾,so凌彬,開始吧。
注:為了簡(jiǎn)單起見循衰,我的例子只是實(shí)現(xiàn)了一個(gè)精簡(jiǎn)的版本铲敛,YYModel 有很多功能,我這里就實(shí)現(xiàn)了一個(gè)核心的功能会钝,JSON -> Model
伐蒋。
注:文章的最后有完整的代碼
從JSON映射到Model的原理
想一下平時(shí)我們是怎么使用類似這樣子的庫(kù)的,當(dāng)我們有一個(gè)JSON的時(shí)候迁酸,我們把所有JSON的字段(比如name先鱼、page)全部寫成對(duì)應(yīng)的類中的屬性。然后庫(kù)會(huì)自動(dòng)把你JSON對(duì)應(yīng)字段的值賦值到這些對(duì)應(yīng)的屬性里去奸鬓。屬性我們用 @property
來(lái)定義焙畔,就意味著編譯器會(huì)幫你生成對(duì)應(yīng)的get``set
方法,我們使用的 .
其實(shí)也是在調(diào)用get``set
方法來(lái)實(shí)現(xiàn)賦值的全蝶。在 Objective-C 中有一個(gè)著名的函數(shù) objc_msgSend(...)
我們所有的類似 [obj method]
的方法調(diào)用(發(fā)送消息)都會(huì)被轉(zhuǎn)換成 objc_msgSend(...)
的方式來(lái)調(diào)用闹蒜。(具體這個(gè)函數(shù)怎么用后面再說(shuō))
所以對(duì)于一個(gè)庫(kù)來(lái)說(shuō),要調(diào)用你這個(gè) Model 的 set
方法抑淫,用 objc_msgSend(...)
會(huì)容易的多绷落,所以JSON映射到Model的原理其實(shí)就是調(diào)用這個(gè)函數(shù)而已。
所以整個(gè)流程就是始苇,你給我一個(gè) Model 類砌烁,我會(huì)用 runtime 提供的各種函數(shù)來(lái)拿到你所有的屬性和對(duì)應(yīng)的get``set
,判斷完相應(yīng)的類型以后催式,調(diào)用objc_msgSend(...)函喉。說(shuō)起來(lái)真的非常簡(jiǎn)單,做起來(lái)就有點(diǎn)麻煩...
前期的準(zhǔn)備工作
為了后面的方便荣月,我們需要把一些關(guān)鍵的東西封裝起來(lái)管呵,我們需要單獨(dú)封裝 ivar
property
method
,也就是實(shí)例變量哺窄、屬性捐下、方法,但事實(shí)上我們的這個(gè)精簡(jiǎn)版的YYModel并不需要 method
ivar
的封裝萌业,為了保證完整性坷襟,我還是打算寫下來(lái)。
封裝 ivar
先來(lái)封裝 ivar
生年,看一下頭文件 CPClassIvarInfo.h
(YYModel只有4個(gè)文件婴程,兩個(gè) .h
兩個(gè) .m
我為了讓代碼看起來(lái)更清楚,所以我自己在重寫 YYModel 的時(shí)候把所有可以拆出來(lái)的類都分別拆成了一對(duì).h .m
)并把前綴改成了 CP 意思是 copy抱婉。
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CPCommon.h"
@interface CPClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *typeEncoding;
@property (nonatomic, assign, readonly) CPEncodingType type;
- (instancetype)initWithIvar:(Ivar)ivar;
@end
Ivar
代表一個(gè)實(shí)例變量档叔,你所有和實(shí)例變量有關(guān)的操作都必須要把 Ivar
傳進(jìn)去,等一下就能看到蒸绩。
name
是這個(gè)實(shí)例變量的變量名
typeEncoding
是對(duì)類型的編碼蹲蒲,具體可以看這里 對(duì)于不同的類型就會(huì)有對(duì)應(yīng)的編碼,比如 int
就會(huì)變編碼成 i
侵贵,可以用 @encode(int)
這樣的操作來(lái)看一個(gè)類型的編碼届搁。
type
是一個(gè)自定義的枚舉,它描述了 YYMode 規(guī)定的類型窍育。
一個(gè)強(qiáng)大的枚舉
然后重新再創(chuàng)建一個(gè)文件(CPCommon)卡睦,作為一個(gè)公共的文件 CPEncodingType
這個(gè)枚舉就寫在這里。
我們要?jiǎng)?chuàng)建的這個(gè)枚舉需要一口氣表示三種不同的類型漱抓,一種用于普通的類型上(int double object
)表锻,一種用來(lái)表示關(guān)鍵詞(const
),一種表示 Property 的屬性(Nonatomic
weak
retain
)乞娄。
我們可以用位運(yùn)算符來(lái)搞定這三種類型瞬逊,用8位的枚舉值來(lái)表示第一種显歧,16位的表示第二種,24位的表示第三種确镊,然后為了區(qū)別這三種類型都屬于多少位的士骤,我們可以分別搞三個(gè) mask ,做一個(gè)該類型和某一個(gè) mask 的與(&)的操作就可以知道這個(gè)類型是具體是哪一個(gè)類型了蕾域,例子在后面拷肌。
這個(gè)枚舉我們可以這樣定義:
typedef NS_OPTIONS(NSUInteger, CPEncodingType) {
CPEncodingTypeMask = 0xFF, //8 bit
CPEncodingTypeUnknown = 0,
CPEncodingTypeVoid = 1,
CPEncodingTypeBool = 2,
CPEncodingTypeInt8 = 3,
CPEncodingTypeUInt8 = 4,
CPEncodingTypeInt16 = 5,
CPEncodingTypeUInt16 = 6,
CPEncodingTypeInt32 = 7,
CPEncodingTypeUInt32 = 8,
CPEncodingTypeInt64 = 9,
CPEncodingTypeUInt64 = 10,
CPEncodingTypeFloat = 11,
CPEncodingTypeDouble = 12,
CPEncodingTypeLongDouble = 13,
CPEncodingTypeObject = 14,
CPEncodingTypeClass = 15,
CPEncodingTypeSEL = 16,
CPEncodingTypeBlock = 17,
CPEncodingTypePointer = 18,
CPEncodingTypeStruct = 19,
CPEncodingTypeUnion = 20,
CPEncodingTypeCString = 21,
CPEncodingTypeCArray = 22,
CPEncodingTypeQualifierMask = 0xFF00, //16 bit
CPEncodingTypeQualifierConst = 1 << 8,
CPEncodingTypeQualifierIn = 1 << 9,
CPEncodingTypeQualifierInout = 1 << 10,
CPEncodingTypeQualifierOut = 1 << 11,
CPEncodingTypeQualifierBycopy = 1 << 12,
CPEncodingTypeQualifierByref = 1 << 13,
CPEncodingTypeQualifierOneway = 1 << 14,
CPEncodingTypePropertyMask = 0xFF0000, // 24 bit
CPEncodingTypePropertyReadonly = 1 << 16,
CPEncodingTypePropertyCopy = 1 << 17,
CPEncodingTypePropertyRetain = 1 << 18,
CPEncodingTypePropertyNonatomic = 1 << 19,
CPEncodingTypePropertyWeak = 1 << 20,
CPEncodingTypePropertyCustomGetter = 1 << 21,
CPEncodingTypePropertyCustomSetter = 1 << 22,
CPEncodingTypePropertyDynamic = 1 << 23,
};
比如有一個(gè)類型是這樣的
CPEncodingType type = CPEncodingTypeDouble;
假設(shè)我們并不知道它是 CPEncodingTypeDouble
類型,那我們要怎么樣才能知道它是什么類型呢旨巷?只要這樣:
NSLog(@"%lu",type & CPEncodingTypeMask);
輸出: 12
在枚舉的定義中
CPEncodingTypeDouble = 12,
假設(shè)這個(gè)枚舉值有很多種混在一起
CPEncodingType type = CPEncodingTypeDouble | CPEncodingTypePropertyRetain;
NSLog(@"%lu",type & CPEncodingTypePropertyMask); //輸出 262144 (1<<18的十進(jìn)制表示)
NSLog(@"%lu",type & CPEncodingTypeMask); //輸出 12
可能有人知道這種神奇的用法巨缘,但在我讀YYModel之前我沒用過這種方法(技術(shù)比較菜)。
然后還有一個(gè)函數(shù)采呐,這個(gè)函數(shù)可以把類型編碼(Type Encoding)轉(zhuǎn)換成剛才的枚舉值若锁,很簡(jiǎn)單卻很長(zhǎng)的一個(gè)函數(shù):
CPEncodingType CPEncodingGetType(const char *typeEncoding) {
char *type = (char *)typeEncoding;
if (!type) return CPEncodingTypeUnknown;
size_t len = strlen(type);
if (len == 0) return CPEncodingTypeUnknown;
CPEncodingType qualifier = 0;
bool prefix = true;
while (prefix) {
switch (*type) {
case 'r': {
qualifier |= CPEncodingTypeQualifierConst;
type++;
} break;
case 'n': {
qualifier |= CPEncodingTypeQualifierIn;
type++;
} break;
case 'N': {
qualifier |= CPEncodingTypeQualifierInout;
type++;
} break;
case 'o': {
qualifier |= CPEncodingTypeQualifierOut;
type++;
} break;
case 'O': {
qualifier |= CPEncodingTypeQualifierBycopy;
type++;
} break;
case 'R': {
qualifier |= CPEncodingTypeQualifierByref;
type++;
} break;
case 'V': {
qualifier |= CPEncodingTypeQualifierOneway;
type++;
} break;
default: { prefix = false; } break;
}
}
len = strlen(type);
if (len == 0) return CPEncodingTypeUnknown | qualifier;
switch (*type) {
case 'v': return CPEncodingTypeVoid | qualifier;
case 'B': return CPEncodingTypeBool | qualifier;
case 'c': return CPEncodingTypeInt8 | qualifier;
case 'C': return CPEncodingTypeUInt8 | qualifier;
case 's': return CPEncodingTypeInt16 | qualifier;
case 'S': return CPEncodingTypeUInt16 | qualifier;
case 'i': return CPEncodingTypeInt32 | qualifier;
case 'I': return CPEncodingTypeUInt32 | qualifier;
case 'l': return CPEncodingTypeInt32 | qualifier;
case 'L': return CPEncodingTypeUInt32 | qualifier;
case 'q': return CPEncodingTypeInt64 | qualifier;
case 'Q': return CPEncodingTypeUInt64 | qualifier;
case 'f': return CPEncodingTypeFloat | qualifier;
case 'd': return CPEncodingTypeDouble | qualifier;
case 'D': return CPEncodingTypeLongDouble | qualifier;
case '#': return CPEncodingTypeClass | qualifier;
case ':': return CPEncodingTypeSEL | qualifier;
case '*': return CPEncodingTypeCString | qualifier;
case '^': return CPEncodingTypePointer | qualifier;
case '[': return CPEncodingTypeCArray | qualifier;
case '(': return CPEncodingTypeUnion | qualifier;
case '{': return CPEncodingTypeStruct | qualifier;
case '@': {
if (len == 2 && *(type + 1) == '?')
return CPEncodingTypeBlock | qualifier;
else
return CPEncodingTypeObject | qualifier;
}
default: return CPEncodingTypeUnknown | qualifier;
}
}
很簡(jiǎn)單,不用多講了斧吐。
回到 CPClassIvarInfo 剛才我們只給出了頭文件拴清,現(xiàn)在看一下實(shí)現(xiàn)。
- (instancetype)initWithIvar:(Ivar)ivar {
if (!ivar){
return nil;
}
self = [super init];
if (self){
_ivar = ivar;
const char *name = ivar_getName(ivar);
if (name){
_name = [NSString stringWithUTF8String:name];
}
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding){
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = CPEncodingGetType(typeEncoding);
}
}
return self;
}
只有一個(gè)方法会通,這里就用到了兩個(gè) runtime 函數(shù) ivar_getName(ivar)
和 ivar_getTypeEncoding(ivar)
傳入 ivar 就行口予。
封裝Method
然后看一下對(duì)于 Method 的封裝,看一下頭文件(CPClassMethodInfo.h)
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
@interface CPClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) SEL sel;
@property (nonatomic, assign, readonly) IMP imp;
@property (nonatomic, strong, readonly) NSString *typeEncoding;
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings;
- (instancetype)initWithMethod:(Method)method;
NS_ASSUME_NONNULL_END
@end
Objective-C 的 Optional
NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
是成對(duì)出現(xiàn)的涕侈,因?yàn)?Swift 可以和 Objective-C 混用沪停,但是 Swift 有 Optional 類型,而 Objective-C 沒有這樣的概念裳涛,為了和 Swift 保持一致木张,現(xiàn)在 Objective-C 有了 _Nullable 可空
_Nonnull不可空
這樣的關(guān)鍵字,這兩個(gè)關(guān)鍵字可以在變量端三、方法返回值舷礼、方法參數(shù)上使用,比如:
@property (nonatomic, strong) NSString * _Nonnull string;
- (NSString * _Nonnull)method:(NSString *_Nonnull)string;
還有另外一對(duì) nullable
nonnull
郊闯,它們可以這樣用
@property (nullable, nonatomic, strong) NSString *string;
- (nullable NSString *)method:(nullable NSString *)string;
對(duì)了妻献,這些關(guān)鍵詞只能用在指針上,其他類型是不能用的团赁。
當(dāng)你一旦在某個(gè)地方寫上關(guān)鍵詞 nullable
的時(shí)候育拨,編譯器就會(huì)提出警告,Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified) 然后你就可以加上NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
來(lái)表示只有我標(biāo)記為 nullable
的地方才可空欢摄,其余地方都是 nonnull
熬丧。
回到剛才的頭文件代碼,method
表示一個(gè)方法
name
很明顯就是方法名了
sel
和 imp
是一個(gè)對(duì)應(yīng)關(guān)系怀挠,一個(gè)對(duì)象的所有方法都會(huì)保存在一張表里,通過 sel
就能找到這個(gè)方法的 imp
,我講的可能有點(diǎn)簡(jiǎn)單,如果想要深入的了解可以查一下文檔或者博客。
typeEncoding
又是一個(gè)編碼播揪,這里是參數(shù)和返回值的編碼
returnTypeEncoding
返回值的編碼
argumentTypeEncodings
所有參數(shù)的編碼
實(shí)現(xiàn)還是很簡(jiǎn)單
- (instancetype)initWithMethod:(Method)method {
if (!method) {
return nil;
}
self = [super init];
if (self){
_method = method;
_sel = method_getName(method);
_imp = method_getImplementation(method);
const char *name = sel_getName(_sel);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
const char *typeEncoding = method_getTypeEncoding(method);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
}
char *returnTypeEncoding = method_copyReturnType(method);
if (returnTypeEncoding) {
_returnTypeEncoding = [NSString stringWithUTF8String:returnTypeEncoding];
free(returnTypeEncoding);
}
//得到參數(shù)的數(shù)目,遍歷取得所有參數(shù)的類型
unsigned int count = method_getNumberOfArguments(method);
if (count > 0) {
NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:10];
for (unsigned int i = 0; i < count; i++) {
char *argumentsType = method_copyArgumentType(method, i);
NSString *type = argumentsType ? [NSString stringWithUTF8String:argumentsType] : nil;
[types addObject:type ? type : @""];
if (argumentsType) {
free(argumentsType);
}
}
_argumentTypeEncodings = types;
}
}
return self;
}
和前面套路一樣。
封裝 Property
老樣子,看頭
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CPCommon.h"
NS_ASSUME_NONNULL_BEGIN
@interface CPClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) CPEncodingType type;
@property (nonatomic, strong, readonly) NSString *typdEncoding;
@property (nonatomic, strong, readonly) NSString *ivarName;
@property (nullable, nonatomic, assign, readonly) Class cls;
@property (nonatomic, assign, readonly) SEL getter;
@property (nonatomic, assign, readonly) SEL setter;
- (instancetype)initWithProperty:(objc_property_t)property;
NS_ASSUME_NONNULL_END
@end
這是在精簡(jiǎn)版的YYModel中會(huì)用到的一個(gè)類,這里尤其要注意的是type
和typdEncoding
兩個(gè)屬性冯吓,希望讀者能夠仔細(xì)調(diào)試一下倘待,看一下主要的一段代碼:
CPEncodingType type = 0;
unsigned int outCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &outCount);
//遍歷所有的Property的屬性
for (unsigned int i = 0; i < outCount; i++) {
switch (attrs[i].name[0]) {
case 'T':
if (attrs[i].value) {
_typdEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = CPEncodingGetType(attrs[i].value);
if((type & CPEncodingTypeMask) == CPEncodingTypeObject){
//如果該類型為一個(gè)對(duì)象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString组贺,目的是為了得到這個(gè)類的 Class
size_t len = strlen(attrs[i].value);
if (len > 3) {
char name[len - 2];
name[len - 3] = '\0';
memcpy(name, attrs[i].value + 2, len - 3);
_cls = objc_getClass(name);
}
}
}
break;
case 'V':
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
}
break;
case 'R':
type |= CPEncodingTypePropertyReadonly;
break;
case 'C':
type |= CPEncodingTypePropertyCopy;
break;
case '&':
type |= CPEncodingTypePropertyRetain;
break;
case 'N':
type |= CPEncodingTypePropertyNonatomic;
break;
case 'D':
type |= CPEncodingTypePropertyDynamic;
break;
case 'W':
type |= CPEncodingTypePropertyWeak;
break;
case 'G':
type |= CPEncodingTypePropertyCustomGetter;
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
break;
case 'S':
type |= CPEncodingTypePropertyCustomSetter;
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
break;
default: break;
}
}
我們通過property_copyAttributeList
這個(gè)函數(shù)得到一個(gè)指向一個(gè)結(jié)構(gòu)體objc_property_attribute_t
的指針凸舵,這個(gè)結(jié)構(gòu)體的結(jié)構(gòu)如下:
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
說(shuō)是一個(gè)指針,其實(shí)它是一個(gè)結(jié)構(gòu)體數(shù)組失尖,指針指向的其實(shí)是這個(gè)數(shù)組第一個(gè)元素啊奄。
這個(gè)結(jié)構(gòu)體表示的是一個(gè) Property 的屬性,關(guān)于 Property 的類型編碼可以看這里
要說(shuō)清這個(gè)數(shù)組里每一個(gè)結(jié)構(gòu)體元素的name
和value
都存了什么掀潮,我們可以看一下下面這段代碼:
Class cls = objc_getClass("CPBook");
objc_property_t property = class_getProperty(cls, "name");
const char* attr = property_getAttributes(property);
NSLog(@"%s",attr);
這里比如有一個(gè)類是 CPBook 菇夸,我們通過這個(gè)類的 Class 來(lái)拿到一個(gè)叫做 name 的 Property,然后在拿到這個(gè) Property 所有屬性仪吧,輸出的結(jié)果是 T@"NSString",&,N,V_name
其實(shí)庄新,我們用和上面一樣返回一個(gè)結(jié)構(gòu)體數(shù)組的方式來(lái)獲取這個(gè) Property 的屬性的話,那么這個(gè)結(jié)構(gòu)體應(yīng)該會(huì)有4個(gè)元素薯鼠。
第一個(gè)元素 name = T
择诈,value = @"NSString"
,第二個(gè)元素 name = &
出皇,value 沒有值
羞芍,第三個(gè)元素 name = N
,value 仍然沒有值
郊艘,第四個(gè)元素 name = V
荷科,value = _name
。不信可以運(yùn)行一下下面的代碼來(lái)看看纱注。
Class cls = objc_getClass("CPBook");
unsigned int acount;
objc_property_t *prop = class_copyPropertyList(cls, &acount);
objc_property_attribute_t *attr1 = property_copyAttributeList(prop[2], &acount);
NSLog(@"%s",attr1[0].name);
NSLog(@"%s",attr1[0].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[1].name);
NSLog(@"%s",attr1[1].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[2].name);
NSLog(@"%s",attr1[2].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[3].name);
NSLog(@"%s",attr1[3].value);
至于 V N & 這樣的符號(hào)是什么意思步做,可以打開上面給出的鏈接自己看一下文檔,一看便知奈附。
這樣一來(lái)在 switch 分支中全度,只要匹配到 T 就能得到這個(gè) Property 的類型是什么,這樣就可以得到這個(gè)類型的 Type Encoding斥滤,并且能夠得到該類的 Class将鸵。只要匹配到 V 就能得到這個(gè) Property 實(shí)例變量名勉盅。
該類全部代碼如下:
- (instancetype)initWithProperty:(objc_property_t)property {
if (!property) {
return nil;
}
self = [super init];
if (self) {
_property = property;
const char *name = property_getName(property);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
CPEncodingType type = 0;
unsigned int outCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &outCount);
//遍歷所有的Property的屬性
for (unsigned int i = 0; i < outCount; i++) {
switch (attrs[i].name[0]) {
case 'T':
if (attrs[i].value) {
_typdEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = CPEncodingGetType(attrs[i].value);
if((type & CPEncodingTypeMask) == CPEncodingTypeObject){
//如果該類型為一個(gè)對(duì)象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString顶掉,目的是為了得到這個(gè)類的 Class
size_t len = strlen(attrs[i].value);
if (len > 3) {
char name[len - 2];
name[len - 3] = '\0';
memcpy(name, attrs[i].value + 2, len - 3);
_cls = objc_getClass(name);
}
}
}
break;
case 'V':
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
}
break;
case 'R':
type |= CPEncodingTypePropertyReadonly;
break;
case 'C':
type |= CPEncodingTypePropertyCopy;
break;
case '&':
type |= CPEncodingTypePropertyRetain;
break;
case 'N':
type |= CPEncodingTypePropertyNonatomic;
break;
case 'D':
type |= CPEncodingTypePropertyDynamic;
break;
case 'W':
type |= CPEncodingTypePropertyWeak;
break;
case 'G':
type |= CPEncodingTypePropertyCustomGetter;
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
break;
case 'S':
type |= CPEncodingTypePropertyCustomSetter;
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
break;
default: break;
}
}
if (attrs) {
free(attrs);
attrs = NULL;
}
_type = type;
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter) {
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
}
return self;
}
這樣一來(lái)草娜,我們就有了 ivar Method Property 的封裝類。接下來(lái)痒筒,我們需要一個(gè)叫做CPClassInfo
的類宰闰,來(lái)封裝一些類的信息,并且把以上三個(gè)類也封裝進(jìn)去簿透,用來(lái)描述整個(gè)類移袍。
封裝 Class
繼續(xù)看頭:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@class CPClassIvarInfo;
@class CPClassMethodInfo;
@class CPClassPropertyInfo;
NS_ASSUME_NONNULL_BEGIN
@interface CPClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls;
@property (nonatomic, assign, readonly) Class superClass;
@property (nonatomic, assign, readonly) Class metaClass;
@property (nonatomic, readonly) BOOL isMeta;
@property (nonatomic, strong, readonly) NSString *name;
@property (nullable, nonatomic, strong, readonly) CPClassInfo *superClassInfo;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, CPClassIvarInfo *> *ivarInfos;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, CPClassMethodInfo *> *methodInfos;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, CPClassPropertyInfo *> *propertyInfos;
- (void)setNeedUpadte;
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;
NS_ASSUME_NONNULL_END
@end
Class
類型用來(lái)描述一個(gè)類,你可以使用
model.class
[model class]
[CPTestModel class]
object_getClass(model)
等方法來(lái)取到這個(gè) Class老充∑系粒·注意object_getClass()
和其他方式 有些不同具體看這里
其余的 Property 不用多介紹了,看到它們的名字就大概能猜到干嘛的了啡浊。
最后的幾個(gè) NSDictionary
用來(lái)存所有的 ivar Method Property觅够。
有些時(shí)候,一個(gè)類有可能被更改巷嚣,可能改掉了方法或者是 Property喘先,那么這時(shí)候應(yīng)該通知CPClassInfo
來(lái)重新獲取到更改過后的類的信息。所以我們有兩個(gè)相關(guān)的方法來(lái)實(shí)現(xiàn)這個(gè)目的廷粒。
- (void)setNeedUpadte;
- (BOOL)needUpdate;
先來(lái)看一下初始化方法
- (instancetype)initWithClass:(Class)cls{
if (!cls) {
return nil;
}
self = [super init];
if (self) {
_cls = cls;
_superClass = class_getSuperclass(cls);
_isMeta = class_isMetaClass(cls);
if (_isMeta) {
_metaClass = objc_getMetaClass(class_getName(cls));
}
_name = NSStringFromClass(cls);
[self _update];
_superClassInfo = [self.class classInfoWithClass:_superClass];
}
return self;
}
你沒看錯(cuò)苹祟,這和頭文件定義的classInfoWithClass:
不是一個(gè)方法,頭文件定義的那個(gè)方法用來(lái)緩存评雌,因?yàn)閷?shí)例化這個(gè)方法還是有點(diǎn)開銷的树枫,所以沒有必要每一次都去實(shí)例化。
這里有一個(gè) _update
方法景东,剛才說(shuō)過砂轻,如果這個(gè)類會(huì)在某一個(gè)時(shí)刻發(fā)生變化,應(yīng)該通知斤吐,收到通知后搔涝,我們?nèi)?zhí)行一些更新的操作,所以把會(huì)發(fā)生變化的一部分代碼單獨(dú)拿出來(lái)更好和措,現(xiàn)在看一下 _update
方法庄呈。
- (void)_update{
_ivarInfos = nil;
_propertyInfos = nil;
_methodInfos = nil;
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(self.cls, &ivarCount);
if (ivars) {
_ivarInfos = [NSMutableDictionary new];
for (unsigned int i = 0; i < ivarCount; i++) {
CPClassIvarInfo *ivarInfo = [[CPClassIvarInfo alloc] initWithIvar:ivars[i]];
if (ivarInfo.name) {
[_ivarInfos setValue:ivarInfo forKey:ivarInfo.name];
}
}
free(ivars);
}
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(self.cls, &propertyCount);
if (properties) {
_propertyInfos = [NSMutableDictionary new];
for (unsigned int i = 0; i < propertyCount; i++) {
CPClassPropertyInfo *propertyInfo = [[CPClassPropertyInfo alloc] initWithProperty:properties[i]];
if (propertyInfo.name) {
[_propertyInfos setValue:propertyInfo forKey:propertyInfo.name];
}
}
free(properties);
}
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(self.cls, &methodCount);
if (methods) {
_methodInfos = [NSMutableDictionary new];
for (unsigned int i = 0; i < methodCount; i++) {
CPClassMethodInfo *methodInfo = [[CPClassMethodInfo alloc] initWithMethod:methods[i]];
if (methodInfo.name) {
[_methodInfos setValue:methodInfo forKey:methodInfo.name];
}
}
free(methods);
}
if (!_ivarInfos) {
_ivarInfos = @{};
}
if (!_methodInfos) {
_methodInfos = @{};
}
if (!_propertyInfos) {
_propertyInfos = @{};
}
_needUpdate = NO;
}
其實(shí)這個(gè)方法就是拿到一個(gè)類所有的 ivar Method Property ,一個(gè)類發(fā)生變化是不是主要就是這三個(gè)玩意的變化派阱?
最后一行的 _needUpdate
是一個(gè)全局變量诬留,用來(lái)標(biāo)識(shí)是否發(fā)生的變化,它被定義在這里,以免暴露給外面文兑。
@implementation CPClassInfo{
BOOL _needUpdate;
}
當(dāng)外界需要通知自己已經(jīng)發(fā)生變化或者查一下是否發(fā)生變化時(shí)就調(diào)用這兩個(gè)相關(guān)方法
- (BOOL)needUpdate {
return _needUpdate;
}
- (void)setNeedUpadte {
_needUpdate = YES;
}
現(xiàn)在來(lái)看一下classInfoWithClass:
+ (instancetype)classInfoWithClass:(Class)cls{
if (!cls) {
return nil;
}
static NSMutableDictionary *metaCache;
static NSMutableDictionary *classCache;
static dispatch_semaphore_t lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
metaCache = [NSMutableDictionary dictionary];
classCache = [NSMutableDictionary dictionary];
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CPClassInfo *info;
if (class_isMetaClass(cls)) {
info = [metaCache valueForKey:NSStringFromClass(cls)];
} else {
info = [classCache valueForKey:NSStringFromClass(cls)];
}
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) {
info = [[CPClassInfo alloc] initWithClass:cls];
if (info) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
if (info.isMeta) {
[metaCache setValue:info forKey:NSStringFromClass(cls)];
} else {
[classCache setValue:info forKey:NSStringFromClass(cls)];
}
dispatch_semaphore_signal(lock);
}
}
return info;
}
兩個(gè) NSMutableDictionary
都是用來(lái)緩存的盒刚,并聲明在了靜態(tài)區(qū),并且使用dispatch_once()
來(lái)確保只會(huì)被初始化一次绿贞,然后我們需要保證線程安全因块,因?yàn)橛锌赡軙?huì)在多線程的場(chǎng)景里被用到,所以使用信號(hào)量dispatch_semaphore_t
來(lái)搞定籍铁,信號(hào)量就像停車這樣的場(chǎng)景一樣涡上,如果發(fā)現(xiàn)車滿了,就等待拒名,一有空位就放行吩愧,也就是說(shuō),當(dāng)一個(gè)線程要進(jìn)入臨界區(qū)的時(shí)候靡狞,必須獲取一個(gè)信號(hào)量耻警,如果沒有問題就進(jìn)入臨界區(qū)隔嫡,這時(shí)另一個(gè)線程進(jìn)來(lái)了甸怕,也要獲取,發(fā)現(xiàn)信號(hào)量并沒有釋放腮恩,就繼續(xù)等待梢杭,直到前面一個(gè)信號(hào)量被釋放后,該線程才準(zhǔn)許進(jìn)入秸滴。我們可以使用dispatch_semaphore_wait()
來(lái)獲取信號(hào)量武契,通過dispatch_semaphore_signal()
來(lái)釋放信號(hào)量。
在這段代碼里荡含,我們首先確保要實(shí)例化的這個(gè)對(duì)象有沒有被緩存咒唆,用傳進(jìn)來(lái)的 cls
作為 key
,如果緩存命中释液,那直接取出緩存全释,然后判斷一下,有沒有更新误债,如果有更新浸船,調(diào)用_update
刷新一遍,返回寝蹈,否則直接返回李命。緩存沒有命中的話,還是乖乖的調(diào)用實(shí)例化方法箫老,然后緩存起來(lái)封字。
繼續(xù)封裝
CPModelPropertyMeta
先建一個(gè)文件,叫做 CPMeta.h
和 CPMeta.m
,我們要在這里寫兩個(gè)類周叮,一個(gè)是對(duì) Property 的再次封裝辩撑,一個(gè)是對(duì) Class 的再次封裝。
我直接把頭文件代碼全拿出來(lái)了:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CPCommon.h"
@class CPClassInfo;
@class CPClassPropertyInfo;
typedef NS_ENUM (NSUInteger, CPEncodingNSType) {
CPEncodingTypeNSUnknown = 0,
CPEncodingTypeNSString,
CPEncodingTypeNSMutableString,
CPEncodingTypeNSValue,
CPEncodingTypeNSNumber,
CPEncodingTypeNSDecimalNumber,
CPEncodingTypeNSData,
CPEncodingTypeNSMutableData,
CPEncodingTypeNSDate,
CPEncodingTypeNSURL,
CPEncodingTypeNSArray,
CPEncodingTypeNSMutableArray,
CPEncodingTypeNSDictionary,
CPEncodingTypeNSMutableDictionary,
CPEncodingTypeNSSet,
CPEncodingTypeNSMutableSet,
};
@interface CPModelMeta : NSObject{
@package
CPClassInfo *_clsInfo;
NSDictionary *_mapper;
NSArray *_allPropertyMetas;
NSUInteger _keyMappedCount;
CPEncodingNSType _nsType;
}
+ (instancetype)metaWithClass:(Class)cls;
@end
@interface CPModelPropertyMeta : NSObject{
@package
NSString *_name;
CPEncodingType _type;
CPEncodingNSType _nsType;
BOOL _isCNumber;
Class _cls;
Class _genericCls;
SEL _getter;
SEL _setter;
BOOL _isKVCCompatible;
NSString *_mappedToKey;
CPClassPropertyInfo *_info;
}
+ (instancetype)modelWithClassInfo:(CPClassInfo *)clsInfo propretyInfo:(CPClassPropertyInfo *)propertyInfo generic:(Class)generic;
@end
可以看到這里有兩個(gè)類仿耽,姑且叫做 CPModelPropertyMeta
和 CPModelMeta
以及一個(gè)枚舉合冀,這個(gè)枚舉表示一個(gè)NS的類型,因?yàn)樵谏弦粋€(gè)枚舉當(dāng)中项贺,我們對(duì)于對(duì)象只定義了 CPEncodingTypeObject
這一個(gè)類型君躺,沒法區(qū)分它到底是 NSString
還是別的,所以這里要細(xì)化一下开缎,類型判斷清楚很重要棕叫,如果不把這部分做好,那么在JSON轉(zhuǎn)換的時(shí)候奕删,類型上出錯(cuò)就直接蹦了俺泣。
先來(lái)看一下 CPModelPropertyMeta
。(在 YYModel 中完残,這兩個(gè)類其實(shí)是和一個(gè)叫做NSObject+CPModel
的擴(kuò)展放在一起的伏钠,但是我強(qiáng)制把它們拆出來(lái)了,為了看起來(lái)清楚谨设,所以我把 @package
的成員變量都寫到了 interface 里面熟掂,這么做是不合理的,但這里為了清晰和學(xué)習(xí)起見扎拣,所以我亂來(lái)了赴肚。)這個(gè)類中多了幾個(gè)成員變量,我就說(shuō)幾個(gè)看起來(lái)不那么清楚的成員變量二蓝。
_isCNumber
這里變量表示是不是一個(gè)C語(yǔ)言的類型誉券,比如int
這樣的。
_genericCls
這個(gè)變量在精簡(jiǎn)版里沒用到刊愚,我只是放在這里踊跟,YYModel 可以給容器型的屬性轉(zhuǎn)換,具體可以看YY大神的文檔百拓。
_isKVCCompatible
能不能支持 KVC
_mappedToKey
要映射的 key琴锭,把 JSON 轉(zhuǎn)成 Model 的時(shí)會(huì)根據(jù)這個(gè) key 把相同字段的 JSON 值賦值給這個(gè) Property。
為了判斷 NS 的類型和是否是 C 類型衙传,在 .m
里有兩個(gè)函數(shù)
#define force_inline __inline__ __attribute__((always_inline))
static force_inline CPEncodingNSType CPClassGetNSType(Class cls) {
if (!cls) return CPEncodingTypeNSUnknown;
if ([cls isSubclassOfClass:[NSMutableString class]]) return CPEncodingTypeNSMutableString;
if ([cls isSubclassOfClass:[NSString class]]) return CPEncodingTypeNSString;
if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return CPEncodingTypeNSDecimalNumber;
if ([cls isSubclassOfClass:[NSNumber class]]) return CPEncodingTypeNSNumber;
if ([cls isSubclassOfClass:[NSValue class]]) return CPEncodingTypeNSValue;
if ([cls isSubclassOfClass:[NSMutableData class]]) return CPEncodingTypeNSMutableData;
if ([cls isSubclassOfClass:[NSData class]]) return CPEncodingTypeNSData;
if ([cls isSubclassOfClass:[NSDate class]]) return CPEncodingTypeNSDate;
if ([cls isSubclassOfClass:[NSURL class]]) return CPEncodingTypeNSURL;
if ([cls isSubclassOfClass:[NSMutableArray class]]) return CPEncodingTypeNSMutableArray;
if ([cls isSubclassOfClass:[NSArray class]]) return CPEncodingTypeNSArray;
if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return CPEncodingTypeNSMutableDictionary;
if ([cls isSubclassOfClass:[NSDictionary class]]) return CPEncodingTypeNSDictionary;
if ([cls isSubclassOfClass:[NSMutableSet class]]) return CPEncodingTypeNSMutableSet;
if ([cls isSubclassOfClass:[NSSet class]]) return CPEncodingTypeNSSet;
return CPEncodingTypeNSUnknown;
}
static force_inline BOOL CPEncodingTypeIsCNumber(CPEncodingType type) {
switch (type & CPEncodingTypeMask) {
case CPEncodingTypeBool:
case CPEncodingTypeInt8:
case CPEncodingTypeUInt8:
case CPEncodingTypeInt16:
case CPEncodingTypeUInt16:
case CPEncodingTypeInt32:
case CPEncodingTypeUInt32:
case CPEncodingTypeInt64:
case CPEncodingTypeUInt64:
case CPEncodingTypeFloat:
case CPEncodingTypeDouble:
case CPEncodingTypeLongDouble: return YES;
default: return NO;
}
}
這兩個(gè)函數(shù)不用多說(shuō)了决帖,很簡(jiǎn)單,要說(shuō)明一下宏定義 force_inline
所有標(biāo)記了 force_inline
的函數(shù)叫做內(nèi)聯(lián)函數(shù)蓖捶,在調(diào)用的時(shí)候都不是一般的調(diào)用地回,而是在編譯的時(shí)候就已經(jīng)整個(gè)丟進(jìn)了調(diào)用這個(gè)函數(shù)的方法或函數(shù)里去了,這和平時(shí)定義一個(gè)宏一樣,你在哪里使用到了這個(gè)宏刻像,那么在編譯的時(shí)候編譯器就會(huì)把你使用這個(gè)宏的地方替換成宏的值畅买。為什么要這么做呢?因?yàn)樾氏杆{(diào)用一個(gè)函數(shù)也是有開銷的谷羞,調(diào)用一個(gè)函數(shù)有壓棧彈棧等操作。如果你的函數(shù)很小溜徙,你這么一弄就免去了這些操作湃缎。
然后看一下CPModelPropertyMeta
的初始化方法
+ (instancetype)modelWithClassInfo:(CPClassInfo *)clsInfo propretyInfo:(CPClassPropertyInfo *)propertyInfo generic:(Class)generic{
CPModelPropertyMeta *meta = [self new];
meta->_name = propertyInfo.name;
meta->_type = propertyInfo.type;
meta->_info = propertyInfo;
meta->_genericCls = generic;
if ((meta->_type & CPEncodingTypeMask) == CPEncodingTypeObject) {
meta->_nsType = CPClassGetNSType(propertyInfo.cls);
} else {
meta->_isCNumber = CPEncodingTypeIsCNumber(meta->_type);
}
meta->_cls = propertyInfo.cls;
if (propertyInfo.getter) {
if ([clsInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
meta->_getter = propertyInfo.getter;
}
}
if (propertyInfo.setter) {
if ([clsInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
meta->_setter = propertyInfo.setter;
}
}
if (meta->_setter && meta->_getter) {
switch (meta->_type & CPEncodingTypeMask) {
case CPEncodingTypeBool:
case CPEncodingTypeInt8:
case CPEncodingTypeUInt8:
case CPEncodingTypeInt16:
case CPEncodingTypeUInt16:
case CPEncodingTypeInt32:
case CPEncodingTypeUInt32:
case CPEncodingTypeInt64:
case CPEncodingTypeUInt64:
case CPEncodingTypeFloat:
case CPEncodingTypeDouble:
case CPEncodingTypeObject:
case CPEncodingTypeClass:
case CPEncodingTypeBlock:
case CPEncodingTypeStruct:
case CPEncodingTypeUnion: {
meta->_isKVCCompatible = YES;
} break;
default: break;
}
}
return meta;
}
判斷一下是否是 object 的類型,然后拿到具體的 NS 類型蠢壹,或者判斷一下是不是 C 類型嗓违,然后拿到 getter
setter
最后判斷一下能不能 KVC。
CPModelPropertyMeta
這個(gè)類主要是生成一個(gè)映射表图贸,這個(gè)映射表就是 _mapper
這個(gè)變量蹂季,這個(gè)類也需要被緩存起來(lái),套路和上面講到的緩存套路一樣
+ (instancetype)metaWithClass:(Class)cls {
if (!cls) return nil;
static CFMutableDictionaryRef cache;
static dispatch_once_t onceToken;
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CPModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);
if (!meta || meta->_clsInfo.needUpdate) {
meta = [[CPModelMeta alloc] initWithClass:cls];
if (meta) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);
}
}
return meta;
}
緩存沒命中就調(diào)用 initWithClass:
來(lái)進(jìn)行初始化
- (instancetype)initWithClass:(Class)cls{
if (!cls) {
return nil;
}
self = [super init];
if (self) {
CPClassInfo *clsInfo = [CPClassInfo classInfoWithClass:cls];
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
CPClassInfo *curClsInfo = clsInfo;
//連同當(dāng)前的類和其父類的屬性一起放入allPropertyMetas數(shù)組疏日,(NSObject和NSProxy是沒有父類的)
while (curClsInfo && curClsInfo.superClass != nil) {
for (CPClassPropertyInfo *propertyInfo in curClsInfo.propertyInfos.allValues) {
if (!propertyInfo.name)continue;
CPModelPropertyMeta *meta = [CPModelPropertyMeta modelWithClassInfo:clsInfo propretyInfo:propertyInfo generic:nil];
if (!meta || !meta->_name)continue;
if (!meta->_setter || !meta->_getter)continue;
if (allPropertyMetas[meta->_name])continue;
allPropertyMetas[meta->_name] = meta;
}
curClsInfo = clsInfo.superClassInfo;
}
if (allPropertyMetas.count) {
_allPropertyMetas = allPropertyMetas.allValues.copy;
}
NSMutableDictionary *mapper = [NSMutableDictionary new];
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull name, CPModelPropertyMeta * _Nonnull meta, BOOL * _Nonnull stop) {
meta->_mappedToKey = name;
mapper[name] = meta;
}];
if (mapper.count) _mapper = mapper;
_clsInfo = clsInfo;
_keyMappedCount = _allPropertyMetas.count;
_nsType = CPClassGetNSType(cls);
}
return self;
}
把 CPClassInfo
里所有的 propertyInfo
遍歷出來(lái)偿洁,實(shí)例化成一個(gè) CPModelPropertyMeta
,還順便把 CPClassInfo
父類的所有 propertyInfo
也拿出來(lái)制恍,這樣一來(lái)父能,你的 Model 即便有一個(gè)父類也能把父類的 Property 賦值神凑。
然后生成一個(gè)映射表净神,就基本完成了初始化工作了,這張映射表是關(guān)鍵溉委,等一下所有的 JSON 的轉(zhuǎn)換都依賴這一張表鹃唯。
從 JSON 到 Model 的轉(zhuǎn)換
現(xiàn)在進(jìn)入正餐環(huán)節(jié),我們剛才已經(jīng)把所有的準(zhǔn)備工作完成了瓣喊,現(xiàn)在要開始正式的完成從 JSON 到 Model 的轉(zhuǎn)換了坡慌。
首先,先建一個(gè) Category藻三,取名 CPModel洪橘,因?yàn)槲覀冎煌瓿烧麄€(gè) YYMode 的一個(gè)主要功能,所以我們只給出一個(gè)接口就行了棵帽,所以頭文件很簡(jiǎn)單熄求。
#import <Foundation/Foundation.h>
@interface NSObject (CPModel)
+ (instancetype)modelWithJSON:(id)json;
@end
使用者只需要調(diào)用 + modelWithJSON:
即可完成轉(zhuǎn)換的操作。
現(xiàn)在看看這個(gè)方法要怎么實(shí)現(xiàn):
+ (instancetype)modelWithJSON:(id)json {
NSDictionary *dic = [self _cp_dictionaryWithJSON:json];
if (!dic || dic == (id)kCFNull) {
return nil;
}
if (![dic isKindOfClass:[NSDictionary class]]) {
return nil;
}
Class cls = [self class];
NSObject *one = [cls new];
if ([one modelSetWithDictionary:dic]) {
return one;
}
return nil;
}
首先先把 JSON 轉(zhuǎn)換成 NSDictionary 逗概,然后得到該 Model 的 Class 去實(shí)例化這個(gè) Model弟晚,接著調(diào)用一個(gè)叫做- modelSetWithDictionary:
的方法。
把 JSON 轉(zhuǎn)換成 NSDictionary 的方法很簡(jiǎn)單
+ (NSDictionary *)_cp_dictionaryWithJSON:(id)json{
if (!json || json == (id)kCFNull) {
return nil;
}
NSDictionary *dic = nil;
NSData *data = nil;
if ([json isKindOfClass:[NSDictionary class]]) {
dic = json;
}else if ([json isKindOfClass:[NSString class]]) {
data = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
}else if ([json isKindOfClass:[NSData class]]) {
data = json;
}
if (data) {
dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
if (![dic isKindOfClass:[NSDictionary class]]) {
dic = nil;
}
}
return dic;
}
然后看一下 - modelSetWithDictionary:
- (BOOL)modelSetWithDictionary:(NSDictionary *)dic{
if (!dic || dic == (id)kCFNull) {
return NO;
}
if (![dic isKindOfClass:[NSDictionary class]]) {
return NO;
}
CPModelMeta *meta = [CPModelMeta metaWithClass:object_getClass(self)]; //①
if (meta->_keyMappedCount == 0) {
return NO;
}
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(meta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
if (meta->_keyMappedCount >= dic.count) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
}
return YES;
}
這里有一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體用來(lái)存儲(chǔ) model
(因?yàn)槭墙o這個(gè)Model 里的 Property 賦值)卿城、modelMeta
(剛才也看到了枚钓,這里存放了映射表)、dictionary
(這是由 JSON 轉(zhuǎn)換過來(lái)的)瑟押,這個(gè)結(jié)構(gòu)體的定義如下:
typedef struct {
void *modelMeta;
void *model;
void *dictionary;
} ModelSetContext;
然后在- modelSetWithDictionary:
有這么一行代碼
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
這個(gè)代碼的作用是搀捷,把一對(duì) Key - Value
拿出來(lái),然后調(diào)用你傳進(jìn)去的函數(shù)ModelSetWithDictionaryFunction()
多望,你有多少對(duì)Key - Value
指煎,它就會(huì)調(diào)用多少次這個(gè)函數(shù),相當(dāng)于便利所有的Key - Value
便斥,為什么要這樣做至壤,而不用一個(gè)循環(huán)呢?在作者的博客里有這么一段
遍歷容器類時(shí)枢纠,選擇更高效的方法
相對(duì)于 Foundation 的方法來(lái)說(shuō)像街,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法來(lái)遍歷容器類能帶來(lái)不少性能提升晋渺,但代碼寫起來(lái)會(huì)非常麻煩镰绎。
然后我們來(lái)看一下ModelSetWithDictionaryFunction()
的實(shí)現(xiàn)
static void ModelSetWithDictionaryFunction(const void *key, const void *value, void *context) {
ModelSetContext *ctx = context;
__unsafe_unretained CPModelMeta *modelMeta = (__bridge CPModelMeta *)(ctx->modelMeta);
__unsafe_unretained CPModelPropertyMeta *propertyMeta = [modelMeta->_mapper objectForKey:(__bridge id)(key)];
__unsafe_unretained id model = (__bridge id)(ctx->model);
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)value, propertyMeta);
}
}
為什么在變量前都加了__unsafe_unretained
,作者也說(shuō)了
避免多余的內(nèi)存管理方法
在 ARC 條件下木西,默認(rèn)聲明的對(duì)象是 __strong 類型的畴栖,賦值時(shí)有可能會(huì)產(chǎn)生 retain/release 調(diào)用,如果一個(gè)變量在其生命周期內(nèi)不會(huì)被釋放八千,則使用 __unsafe_unretained 會(huì)節(jié)省很大的開銷吗讶。
訪問具有 __weak 屬性的變量時(shí),實(shí)際上會(huì)調(diào)用 objc_loadWeak() 和 objc_storeWeak() 來(lái)完成恋捆,這也會(huì)帶來(lái)很大的開銷照皆,所以要避免使用 __weak 屬性。
繼續(xù)沸停,根據(jù) key(這個(gè) key 就是 JSON 里的字段膜毁,應(yīng)該和你 Model 定義的 Property 名相同,否則就匹配不了愤钾,在 YYMode 中有一個(gè)自定義映射表的支持瘟滨,我把它去掉了,有興趣的可以下載 YYMode 的源碼看一下) 取出映射表里的 propertyMeta
∧馨洌現(xiàn)在我們有了要轉(zhuǎn)換的 model 對(duì)象杂瘸,和一個(gè)和 JSON 里字段對(duì)應(yīng)的 propertyMeta
對(duì)象,已經(jīng)該 JSON 字段的值劲装,現(xiàn)在要賦值的條件全部具備了胧沫,我們只需要調(diào)用propertyMeta
中的setter
方法昌简,然后把值傳進(jìn)去就完成了,這部分的工作由 ModelSetValueForProperty()
函數(shù)完成绒怨,這個(gè)函數(shù)里有大量的類型判斷纯赎,為了簡(jiǎn)單起見,我就判斷了NSString
NSNumber
和普通C語(yǔ)言類型南蹂,代碼如下:
static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained CPModelPropertyMeta *meta) {
if (meta->_isCNumber) {
NSNumber *num = CPNSNumberCreateFromID(value);
ModelSetNumberToProperty(model, num, meta);
if (num) [num class];
} else if (meta->_nsType) {
if (value == (id)kCFNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else {
switch (meta->_nsType) {
case CPEncodingTypeNSString:
case CPEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == CPEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
}
} else if ([value isKindOfClass:[NSNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == CPEncodingTypeNSString) ?
((NSNumber *)value).stringValue :
((NSNumber *)value).stringValue.mutableCopy);
} else if ([value isKindOfClass:[NSData class]]) {
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
} else if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == CPEncodingTypeNSString) ?
((NSURL *)value).absoluteString :
((NSURL *)value).absoluteString.mutableCopy);
} else if ([value isKindOfClass:[NSAttributedString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == CPEncodingTypeNSString) ?
((NSAttributedString *)value).string :
((NSAttributedString *)value).string.mutableCopy);
}
} break;
case CPEncodingTypeNSNumber:{
if ([value isKindOfClass:[NSNumber class]]) {
if (meta->_nsType == CPEncodingTypeNSNumber) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,meta->_setter,value);
}
}
} break;
default: break;
}
}
}
}
關(guān)于 objc_msgSend()
我們隨便拿一行例子來(lái)舉例犬金,比如這個(gè):
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
這是一個(gè)可以調(diào)用者決定返回值和參數(shù)的函數(shù),一般的函數(shù)是做不到的六剥,默認(rèn)情況下這個(gè)函數(shù)是長(zhǎng)這樣
objc_msgSend(id, SEL)
id
是指調(diào)用某一個(gè)方法的對(duì)象晚顷,在這里這個(gè)對(duì)象就是你的 Model
SEL
是指你這個(gè)對(duì)象要調(diào)用的方法是什么,在這里這個(gè)方法就是 setter
方法
然而疗疟,setter
方法是有參數(shù)的该默,這個(gè)參數(shù)怎么傳進(jìn)去?這就需要強(qiáng)制類型轉(zhuǎn)換了策彤,我們把這個(gè)函數(shù)強(qiáng)制轉(zhuǎn)換成這個(gè)模樣:
((void (*)(id, SEL, id))(void *) objc_msgSend)
這樣代表這個(gè)函數(shù)是一個(gè)沒有返回值栓袖,并且有3個(gè)參數(shù)的函數(shù),分別是 id
SEL
id
店诗,前面兩個(gè)參數(shù)之前講過了裹刮,第三個(gè)參數(shù)就是你要調(diào)用的這個(gè) setter
方法需要的參數(shù),所以經(jīng)過強(qiáng)制類型轉(zhuǎn)換之后的變異版就成了一開始的那種樣子庞瘸。
其余的都沒有什么好講的了捧弃,都很簡(jiǎn)單,都是一些煩人的類型判斷擦囊,只要仔細(xì)一點(diǎn)一行行看就能看懂了违霞。
全部搞定以后,和原版的 YYModel 一樣霜第,你可以這么來(lái)測(cè)試
CPTestModel *model = [CPTestModel modelWithJSON:@"{\"name\": \"Harry Potter\",\"index\": 512,\"number\": 10,\"num\": 100}"];
結(jié)尾
如果你自己親自動(dòng)手寫完了這個(gè)精簡(jiǎn)版的 YYMode 葛家,你再去看完整版的會(huì)容易很多户辞,我寫的這篇文章是把我從讀 YYModel 源碼中學(xué)到的一些有用的東西分享給大家泌类,如有什么寫錯(cuò)的地方,歡迎指正底燎。
完整代碼
點(diǎn)擊這里