讀完這篇文章你可以自己寫一個 YYModel 這樣的神器,這篇文章類似一個源碼解析,但不同的是,它不光光是解析,更是實戰(zhàn),因為我覺得學(xué)習(xí)一個東西必須要自己寫一遍才算是真的學(xué)了一遍,否則即便是讀完了源碼印象還是不會太深刻,so,開始吧。
注:為了簡單起見,我的例子只是實現(xiàn)了一個精簡的版本,YYModel 有很多功能,我這里就實現(xiàn)了一個核心的功能,JSON -> Model
。
注:文章的最后有完整的代碼
從JSON映射到Model的原理
想一下平時我們是怎么使用類似這樣子的庫的,當我們有一個JSON的時候,我們把所有JSON的字段(比如name、page)全部寫成對應(yīng)的類中的屬性。然后庫會自動把你JSON對應(yīng)字段的值賦值到這些對應(yīng)的屬性里去。屬性我們用 @property
來定義,就意味著編譯器會幫你生成對應(yīng)的get``set
方法,我們使用的 .
其實也是在調(diào)用get``set
方法來實現(xiàn)賦值的。在 Objective-C 中有一個著名的函數(shù) objc_msgSend(...)
我們所有的類似 [obj method]
的方法調(diào)用(發(fā)送消息)都會被轉(zhuǎn)換成 objc_msgSend(...)
的方式來調(diào)用。(具體這個函數(shù)怎么用后面再說)
所以對于一個庫來說,要調(diào)用你這個 Model 的 set
方法,用 objc_msgSend(...)
會容易的多,所以JSON映射到Model的原理其實就是調(diào)用這個函數(shù)而已。
所以整個流程就是,你給我一個 Model 類,我會用 runtime 提供的各種函數(shù)來拿到你所有的屬性和對應(yīng)的get``set
,判斷完相應(yīng)的類型以后,調(diào)用objc_msgSend(...)。說起來真的非常簡單,做起來就有點麻煩...
前期的準備工作
為了后面的方便,我們需要把一些關(guān)鍵的東西封裝起來,我們需要單獨封裝 ivar
property
method
,也就是實例變量、屬性、方法,但事實上我們的這個精簡版的YYModel并不需要 method
ivar
的封裝,為了保證完整性,我還是打算寫下來。
封裝 ivar
先來封裝 ivar
,看一下頭文件 CPClassIvarInfo.h
(YYModel只有4個文件,兩個 .h
兩個 .m
我為了讓代碼看起來更清楚,所以我自己在重寫 YYModel 的時候把所有可以拆出來的類都分別拆成了一對.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
代表一個實例變量,你所有和實例變量有關(guān)的操作都必須要把 Ivar
傳進去,等一下就能看到。
name
是這個實例變量的變量名
typeEncoding
是對類型的編碼,具體可以看這里 對于不同的類型就會有對應(yīng)的編碼,比如 int
就會變編碼成 i
,可以用 @encode(int)
這樣的操作來看一個類型的編碼。
type
是一個自定義的枚舉,它描述了 YYMode 規(guī)定的類型。
一個強大的枚舉
然后重新再創(chuàng)建一個文件(CPCommon),作為一個公共的文件 CPEncodingType
這個枚舉就寫在這里。
我們要創(chuàng)建的這個枚舉需要一口氣表示三種不同的類型,一種用于普通的類型上(int double object
),一種用來表示關(guān)鍵詞(const
),一種表示 Property 的屬性(Nonatomic
weak
retain
)。
我們可以用位運算符來搞定這三種類型,用8位的枚舉值來表示第一種,16位的表示第二種,24位的表示第三種,然后為了區(qū)別這三種類型都屬于多少位的,我們可以分別搞三個 mask ,做一個該類型和某一個 mask 的與(&)的操作就可以知道這個類型是具體是哪一個類型了,例子在后面。
這個枚舉我們可以這樣定義:
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,
};
比如有一個類型是這樣的
CPEncodingType type = CPEncodingTypeDouble;
假設(shè)我們并不知道它是 CPEncodingTypeDouble
類型,那我們要怎么樣才能知道它是什么類型呢?只要這樣:
NSLog(@"%lu",type & CPEncodingTypeMask);
輸出: 12
在枚舉的定義中
CPEncodingTypeDouble = 12,
假設(shè)這個枚舉值有很多種混在一起
CPEncodingType type = CPEncodingTypeDouble | CPEncodingTypePropertyRetain;
NSLog(@"%lu",type & CPEncodingTypePropertyMask); //輸出 262144 (1<<18的十進制表示)
NSLog(@"%lu",type & CPEncodingTypeMask); //輸出 12
可能有人知道這種神奇的用法,但在我讀YYModel之前我沒用過這種方法(技術(shù)比較菜)。
然后還有一個函數(shù),這個函數(shù)可以把類型編碼(Type Encoding)轉(zhuǎn)換成剛才的枚舉值,很簡單卻很長的一個函數(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;
}
}
很簡單,不用多講了。
回到 CPClassIvarInfo 剛才我們只給出了頭文件,現(xiàn)在看一下實現(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;
}
只有一個方法,這里就用到了兩個 runtime 函數(shù) ivar_getName(ivar)
和 ivar_getTypeEncoding(ivar)
傳入 ivar 就行。
封裝Method
然后看一下對于 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
是成對出現(xiàn)的,因為 Swift 可以和 Objective-C 混用,但是 Swift 有 Optional 類型,而 Objective-C 沒有這樣的概念,為了和 Swift 保持一致,現(xiàn)在 Objective-C 有了 _Nullable 可空
_Nonnull不可空
這樣的關(guān)鍵字,這兩個關(guān)鍵字可以在變量、方法返回值、方法參數(shù)上使用,比如:
@property (nonatomic, strong) NSString * _Nonnull string;
- (NSString * _Nonnull)method:(NSString *_Nonnull)string;
還有另外一對 nullable
nonnull
,它們可以這樣用
@property (nullable, nonatomic, strong) NSString *string;
- (nullable NSString *)method:(nullable NSString *)string;
對了,這些關(guān)鍵詞只能用在指針上,其他類型是不能用的。
當你一旦在某個地方寫上關(guān)鍵詞 nullable
的時候,編譯器就會提出警告,Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified) 然后你就可以加上NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
來表示只有我標記為 nullable
的地方才可空,其余地方都是 nonnull
。
回到剛才的頭文件代碼,method
表示一個方法
name
很明顯就是方法名了
sel
和 imp
是一個對應(yīng)關(guān)系,一個對象的所有方法都會保存在一張表里,通過 sel
就能找到這個方法的 imp
,我講的可能有點簡單,如果想要深入的了解可以查一下文檔或者博客。
typeEncoding
又是一個編碼,這里是參數(shù)和返回值的編碼
returnTypeEncoding
返回值的編碼
argumentTypeEncodings
所有參數(shù)的編碼
實現(xià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
這是在精簡版的YYModel中會用到的一個類,這里尤其要注意的是type
和typdEncoding
兩個屬性,希望讀者能夠仔細調(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){
//如果該類型為一個對象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString,目的是為了得到這個類的 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
這個函數(shù)得到一個指向一個結(jié)構(gòu)體objc_property_attribute_t
的指針,這個結(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;
說是一個指針,其實它是一個結(jié)構(gòu)體數(shù)組,指針指向的其實是這個數(shù)組第一個元素。
這個結(jié)構(gòu)體表示的是一個 Property 的屬性,關(guān)于 Property 的類型編碼可以看這里
要說清這個數(shù)組里每一個結(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);
這里比如有一個類是 CPBook ,我們通過這個類的 Class 來拿到一個叫做 name 的 Property,然后在拿到這個 Property 所有屬性,輸出的結(jié)果是 T@"NSString",&,N,V_name
其實,我們用和上面一樣返回一個結(jié)構(gòu)體數(shù)組的方式來獲取這個 Property 的屬性的話,那么這個結(jié)構(gòu)體應(yīng)該會有4個元素。
第一個元素 name = T
,value = @"NSString"
,第二個元素 name = &
,value 沒有值
,第三個元素 name = N
,value 仍然沒有值
,第四個元素 name = V
,value = _name
。不信可以運行一下下面的代碼來看看。
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 & 這樣的符號是什么意思,可以打開上面給出的鏈接自己看一下文檔,一看便知。
這樣一來在 switch 分支中,只要匹配到 T 就能得到這個 Property 的類型是什么,這樣就可以得到這個類型的 Type Encoding,并且能夠得到該類的 Class。只要匹配到 V 就能得到這個 Property 實例變量名。
該類全部代碼如下:
- (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){
//如果該類型為一個對象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString,目的是為了得到這個類的 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;
}
這樣一來,我們就有了 ivar Method Property 的封裝類。接下來,我們需要一個叫做CPClassInfo
的類,來封裝一些類的信息,并且把以上三個類也封裝進去,用來描述整個類。
封裝 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
類型用來描述一個類,你可以使用
model.class
[model class]
[CPTestModel class]
object_getClass(model)
等方法來取到這個 Class。·注意object_getClass()
和其他方式 有些不同具體看這里
其余的 Property 不用多介紹了,看到它們的名字就大概能猜到干嘛的了。
最后的幾個 NSDictionary
用來存所有的 ivar Method Property。
有些時候,一個類有可能被更改,可能改掉了方法或者是 Property,那么這時候應(yīng)該通知CPClassInfo
來重新獲取到更改過后的類的信息。所以我們有兩個相關(guān)的方法來實現(xiàn)這個目的。
- (void)setNeedUpadte;
- (BOOL)needUpdate;
先來看一下初始化方法
- (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;
}
你沒看錯,這和頭文件定義的classInfoWithClass:
不是一個方法,頭文件定義的那個方法用來緩存,因為實例化這個方法還是有點開銷的,所以沒有必要每一次都去實例化。
這里有一個 _update
方法,剛才說過,如果這個類會在某一個時刻發(fā)生變化,應(yīng)該通知,收到通知后,我們?nèi)?zhí)行一些更新的操作,所以把會發(fā)生變化的一部分代碼單獨拿出來更好,現(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;
}
其實這個方法就是拿到一個類所有的 ivar Method Property ,一個類發(fā)生變化是不是主要就是這三個玩意的變化?
最后一行的 _needUpdate
是一個全局變量,用來標識是否發(fā)生的變化,它被定義在這里,以免暴露給外面。
@implementation CPClassInfo{
BOOL _needUpdate;
}
當外界需要通知自己已經(jīng)發(fā)生變化或者查一下是否發(fā)生變化時就調(diào)用這兩個相關(guān)方法
- (BOOL)needUpdate {
return _needUpdate;
}
- (void)setNeedUpadte {
_needUpdate = YES;
}
現(xiàn)在來看一下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;
}
兩個 NSMutableDictionary
都是用來緩存的,并聲明在了靜態(tài)區(qū),并且使用dispatch_once()
來確保只會被初始化一次,然后我們需要保證線程安全,因為有可能會在多線程的場景里被用到,所以使用信號量dispatch_semaphore_t
來搞定,信號量就像停車這樣的場景一樣,如果發(fā)現(xiàn)車滿了,就等待,一有空位就放行,也就是說,當一個線程要進入臨界區(qū)的時候,必須獲取一個信號量,如果沒有問題就進入臨界區(qū),這時另一個線程進來了,也要獲取,發(fā)現(xiàn)信號量并沒有釋放,就繼續(xù)等待,直到前面一個信號量被釋放后,該線程才準許進入。我們可以使用dispatch_semaphore_wait()
來獲取信號量,通過dispatch_semaphore_signal()
來釋放信號量。
在這段代碼里,我們首先確保要實例化的這個對象有沒有被緩存,用傳進來的 cls
作為 key
,如果緩存命中,那直接取出緩存,然后判斷一下,有沒有更新,如果有更新,調(diào)用_update
刷新一遍,返回,否則直接返回。緩存沒有命中的話,還是乖乖的調(diào)用實例化方法,然后緩存起來。
繼續(xù)封裝
CPModelPropertyMeta
先建一個文件,叫做 CPMeta.h
和 CPMeta.m
,我們要在這里寫兩個類,一個是對 Property 的再次封裝,一個是對 Class 的再次封裝。
我直接把頭文件代碼全拿出來了:
#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
可以看到這里有兩個類,姑且叫做 CPModelPropertyMeta
和 CPModelMeta
以及一個枚舉,這個枚舉表示一個NS的類型,因為在上一個枚舉當中,我們對于對象只定義了 CPEncodingTypeObject
這一個類型,沒法區(qū)分它到底是 NSString
還是別的,所以這里要細化一下,類型判斷清楚很重要,如果不把這部分做好,那么在JSON轉(zhuǎn)換的時候,類型上出錯就直接蹦了。
先來看一下 CPModelPropertyMeta
。(在 YYModel 中,這兩個類其實是和一個叫做NSObject+CPModel
的擴展放在一起的,但是我強制把它們拆出來了,為了看起來清楚,所以我把 @package
的成員變量都寫到了 interface 里面,這么做是不合理的,但這里為了清晰和學(xué)習(xí)起見,所以我亂來了。)這個類中多了幾個成員變量,我就說幾個看起來不那么清楚的成員變量。
_isCNumber
這里變量表示是不是一個C語言的類型,比如int
這樣的。
_genericCls
這個變量在精簡版里沒用到,我只是放在這里,YYModel 可以給容器型的屬性轉(zhuǎn)換,具體可以看YY大神的文檔。
_isKVCCompatible
能不能支持 KVC
_mappedToKey
要映射的 key,把 JSON 轉(zhuǎn)成 Model 的時會根據(jù)這個 key 把相同字段的 JSON 值賦值給這個 Property。
為了判斷 NS 的類型和是否是 C 類型,在 .m
里有兩個函數(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;
}
}
這兩個函數(shù)不用多說了,很簡單,要說明一下宏定義 force_inline
所有標記了 force_inline
的函數(shù)叫做內(nèi)聯(lián)函數(shù),在調(diào)用的時候都不是一般的調(diào)用,而是在編譯的時候就已經(jīng)整個丟進了調(diào)用這個函數(shù)的方法或函數(shù)里去了,這和平時定義一個宏一樣,你在哪里使用到了這個宏,那么在編譯的時候編譯器就會把你使用這個宏的地方替換成宏的值。為什么要這么做呢?因為效率,調(diào)用一個函數(shù)也是有開銷的,調(diào)用一個函數(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
這個類主要是生成一個映射表,這個映射表就是 _mapper
這個變量,這個類也需要被緩存起來,套路和上面講到的緩存套路一樣
+ (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:
來進行初始化
- (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;
//連同當前的類和其父類的屬性一起放入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
遍歷出來,實例化成一個 CPModelPropertyMeta
,還順便把 CPClassInfo
父類的所有 propertyInfo
也拿出來,這樣一來,你的 Model 即便有一個父類也能把父類的 Property 賦值。
然后生成一個映射表,就基本完成了初始化工作了,這張映射表是關(guān)鍵,等一下所有的 JSON 的轉(zhuǎn)換都依賴這一張表。
從 JSON 到 Model 的轉(zhuǎn)換
現(xiàn)在進入正餐環(huán)節(jié),我們剛才已經(jīng)把所有的準備工作完成了,現(xiàn)在要開始正式的完成從 JSON 到 Model 的轉(zhuǎn)換了。
首先,先建一個 Category,取名 CPModel,因為我們只完成整個 YYMode 的一個主要功能,所以我們只給出一個接口就行了,所以頭文件很簡單。
#import <Foundation/Foundation.h>
@interface NSObject (CPModel)
+ (instancetype)modelWithJSON:(id)json;
@end
使用者只需要調(diào)用 + modelWithJSON:
即可完成轉(zhuǎn)換的操作。
現(xiàn)在看看這個方法要怎么實現(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 去實例化這個 Model,接著調(diào)用一個叫做- modelSetWithDictionary:
的方法。
把 JSON 轉(zhuǎn)換成 NSDictionary 的方法很簡單
+ (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;
}
這里有一個結(jié)構(gòu)體,這個結(jié)構(gòu)體用來存儲 model
(因為是給這個Model 里的 Property 賦值)、modelMeta
(剛才也看到了,這里存放了映射表)、dictionary
(這是由 JSON 轉(zhuǎn)換過來的),這個結(jié)構(gòu)體的定義如下:
typedef struct {
void *modelMeta;
void *model;
void *dictionary;
} ModelSetContext;
然后在- modelSetWithDictionary:
有這么一行代碼
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
這個代碼的作用是,把一對 Key - Value
拿出來,然后調(diào)用你傳進去的函數(shù)ModelSetWithDictionaryFunction()
,你有多少對Key - Value
,它就會調(diào)用多少次這個函數(shù),相當于便利所有的Key - Value
,為什么要這樣做,而不用一個循環(huán)呢?在作者的博客里有這么一段
遍歷容器類時,選擇更高效的方法
相對于 Foundation 的方法來說,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法來遍歷容器類能帶來不少性能提升,但代碼寫起來會非常麻煩。
然后我們來看一下ModelSetWithDictionaryFunction()
的實現(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
,作者也說了
避免多余的內(nèi)存管理方法
在 ARC 條件下,默認聲明的對象是 __strong 類型的,賦值時有可能會產(chǎn)生 retain/release 調(diào)用,如果一個變量在其生命周期內(nèi)不會被釋放,則使用 __unsafe_unretained 會節(jié)省很大的開銷。
訪問具有 __weak 屬性的變量時,實際上會調(diào)用 objc_loadWeak() 和 objc_storeWeak() 來完成,這也會帶來很大的開銷,所以要避免使用 __weak 屬性。
繼續(xù),根據(jù) key(這個 key 就是 JSON 里的字段,應(yīng)該和你 Model 定義的 Property 名相同,否則就匹配不了,在 YYMode 中有一個自定義映射表的支持,我把它去掉了,有興趣的可以下載 YYMode 的源碼看一下) 取出映射表里的 propertyMeta
。現(xiàn)在我們有了要轉(zhuǎn)換的 model 對象,和一個和 JSON 里字段對應(yīng)的 propertyMeta
對象,已經(jīng)該 JSON 字段的值,現(xiàn)在要賦值的條件全部具備了,我們只需要調(diào)用propertyMeta
中的setter
方法,然后把值傳進去就完成了,這部分的工作由 ModelSetValueForProperty()
函數(shù)完成,這個函數(shù)里有大量的類型判斷,為了簡單起見,我就判斷了NSString
NSNumber
和普通C語言類型,代碼如下:
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()
我們隨便拿一行例子來舉例,比如這個:
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
這是一個可以調(diào)用者決定返回值和參數(shù)的函數(shù),一般的函數(shù)是做不到的,默認情況下這個函數(shù)是長這樣
objc_msgSend(id, SEL)
id
是指調(diào)用某一個方法的對象,在這里這個對象就是你的 Model
SEL
是指你這個對象要調(diào)用的方法是什么,在這里這個方法就是 setter
方法
然而,setter
方法是有參數(shù)的,這個參數(shù)怎么傳進去?這就需要強制類型轉(zhuǎn)換了,我們把這個函數(shù)強制轉(zhuǎn)換成這個模樣:
((void (*)(id, SEL, id))(void *) objc_msgSend)
這樣代表這個函數(shù)是一個沒有返回值,并且有3個參數(shù)的函數(shù),分別是 id
SEL
id
,前面兩個參數(shù)之前講過了,第三個參數(shù)就是你要調(diào)用的這個 setter
方法需要的參數(shù),所以經(jīng)過強制類型轉(zhuǎn)換之后的變異版就成了一開始的那種樣子。
其余的都沒有什么好講的了,都很簡單,都是一些煩人的類型判斷,只要仔細一點一行行看就能看懂了。
全部搞定以后,和原版的 YYModel 一樣,你可以這么來測試
CPTestModel *model = [CPTestModel modelWithJSON:@"{\"name\": \"Harry Potter\",\"index\": 512,\"number\": 10,\"num\": 100}"];
結(jié)尾
如果你自己親自動手寫完了這個精簡版的 YYMode ,你再去看完整版的會容易很多,我寫的這篇文章是把我從讀 YYModel 源碼中學(xué)到的一些有用的東西分享給大家,如有什么寫錯的地方,歡迎指正。
完整代碼
點擊這里