預(yù)備知識(shí)
理解Class與MetalClass
下面這張圖很重要野舶,得記住哦。具體的介紹宰衙,還是看權(quán)威的文章吧??平道,參考文章1、文章2;
個(gè)人理解供炼,在我們獲取每一個(gè)類一屋、實(shí)例對(duì)象的Class時(shí)窘疮,實(shí)際上獲取的是isa對(duì)象,正如上圖所示冀墨,下面的示例代碼也說(shuō)明了情況闸衫, 對(duì)object1、object2對(duì)象獲取class時(shí)獲取的是各自的isa對(duì)像得到class1轧苫、class2楚堤,實(shí)際上class1、class2為同一個(gè)對(duì)象含懊,當(dāng)再次獲取class1、class2的class時(shí)獲取的就是元類了:
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class class1 = [object1 class];
Class class2 = [object2 class];
NSLog(@"class1:%p, class2:-%p", class1, class2); // class1:0x1b7af3ea0, class2:-0x1b7af3ea0
Class metaClass1 = object_getClass(class1);
Class metaClass2 = object_getClass(class2);
NSLog(@"metaClass1:%p, metaClass2:-%p", metaClass1, metaClass2); // metaClass1:0x1b7af3ec8, metaClass2:-0x1b7af3ec8
Class metaClass3 = object_getClass(metaClass1);
Class metaClass4 = object_getClass(metaClass2);
NSLog(@"metaClass3:%p, metaClass4:-%p", metaClass3, metaClass4); // metaClass3:0x1b7af3ec8, metaClass4:-0x1b7af3ec8
Method
Method本身是一個(gè)objc_method類型的結(jié)構(gòu)體指針即方法的具體表示衅胀,objc_method是一個(gè)結(jié)構(gòu)體岔乔, 其定義如下:
struct objc_method {
SEL _Nonnull method_name 方法名;
char * _Nullable method_types 方法類型, 即參數(shù)、返回值類型;
IMP _Nonnull method_imp 方法對(duì)應(yīng)的函數(shù)指針;
}
// 獲取類所有的方法
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
method_getName(method) // 獲取方法的名稱滚躯,返回的是一個(gè)SEL類型
method_getImplementation(method) // 獲取方法的函數(shù)指針
sel_getName(_sel) // 將SEL類型轉(zhuǎn)化為C字符串
method_getTypeEncoding(method) // 獲取方法的參數(shù)雏门、返回值類型,例如:“v16@0:8”
method_copyReturnType(method) // 獲取方法的返回值類型掸掏,例如:“v”
method_getNumberOfArguments(method) // 獲取方法參數(shù)的個(gè)數(shù)
method_copyArgumentType(method, i) // 獲取方法指定參數(shù)的類型茁影, 例如“@”
objc_property、類型編碼
objc_property_t本身是一個(gè)objc_property類型的結(jié)構(gòu)體指針即屬性的具體表示丧凤,我承認(rèn)我沒(méi)有找到objc_property的定義募闲,但是找到一個(gè)objc_property_attribute_t結(jié)構(gòu)體,它是屬性的attribute愿待,也就是其實(shí)是對(duì)屬性的詳細(xì)描述浩螺,包括屬性名稱、屬性編碼類型仍侥、原子類型/非原子類型等要出,可以參考類型編碼的文章。它的定義如下:
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
// 通過(guò)property_copyAttributeList(property, &attrCount)函數(shù)可以獲取一個(gè)存有該結(jié)構(gòu)體的數(shù)組农渊。
該結(jié)構(gòu)體的name和value均為char型的指針患蹂,也就是一個(gè)C字符串,在YYModel中可以看到對(duì)于他們的使用是獲取name[0]砸紊、*name的值传于,也就是獲取C字符串的第一個(gè)字符。需要注意的是,當(dāng)name[0]為‘T’時(shí)批糟,證明此時(shí)的objc_property_attribute_t
結(jié)構(gòu)體描述的是屬性對(duì)應(yīng)的數(shù)據(jù)類型格了,是BOOL
還是NSString
類型等。再比如name[0]為’N‘時(shí)表明該結(jié)構(gòu)體描述的是屬性的原子性徽鼎,即為nonatomic
盛末。另外當(dāng)name[0]為’G‘弹惦、’S‘時(shí)分別表示該屬性自定義了getter、setter方法悄但。
// 獲取所有的屬性
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
property_getName(property) // 獲取屬性的名稱
property_copyAttributeList(property, &attrCount) // 獲取屬性的詳細(xì)信息,返回一個(gè)數(shù)組
備注:所有通過(guò)C函數(shù)所獲取的數(shù)據(jù)棠隐,都應(yīng)該手動(dòng)調(diào)用free()函數(shù)釋放內(nèi)存
Ivar
Ivar本身是一個(gè)objc_ivar類型的結(jié)構(gòu)體指針即實(shí)例變量的具體表示,objc_ivar是一個(gè)結(jié)構(gòu)體檐嚣, 其定義如下:
struct objc_ivar {
char * _Nullable ivar_name 變量名;
char * _Nullable ivar_type 變量類型;
int ivar_offset 基地址偏移字節(jié);
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
ivar_getName(ivar) // 獲取實(shí)例變量名稱
ivar_getOffset(ivar) // 獲取基地址偏移字節(jié)
ivar_getTypeEncoding(ivar) // 獲取變量類型
NSScanner
NSScanner是一個(gè)類助泽,用于在字符串中掃描指定的字符,尤其是把它們翻譯/轉(zhuǎn)換為數(shù)字和別的字符串嚎京∥撕兀可以在創(chuàng)建NSScaner時(shí)指定它的string屬性,然后scanner會(huì)按照你的要求從頭到尾地掃描這個(gè)字符串的每個(gè)字符鞍帝。
對(duì)于查詢字符串而言诫睬,這真是一個(gè)強(qiáng)大的類,下面簡(jiǎn)單學(xué)習(xí)下YYModel是如何利用該類進(jìn)行類型編碼解析的:
// 該類用于解析屬性的數(shù)據(jù)類型帕涌,YYModel考慮得很全面摄凡,連遵循協(xié)議的id類型都有考慮
// 如果解析到是一個(gè)NSObject對(duì)象則進(jìn)行如下詳細(xì)的解析,例如:NSString類型時(shí)_typeEncoding為@"NSString"
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
// 用需要掃描的_typeEncoding字符串蚓曼,初始化scanner對(duì)象
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
// 我們的目的是拿到NSString亲澡,所以需要更改scanner掃描的起始位置為字符N所在的位置
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
NSString *clsName = nil;
// 開(kāi)始掃描_typeEncoding直到遇到字符”或者<,遇到了則返回YES纫版,并將掃描到的字符串存入clsName床绪。
// 需要注意的是NSCharacterSet其實(shí)設(shè)置的是停止掃描的字符,如果該字符出現(xiàn)在NSScanner掃描的起始位置捎琐,該方法會(huì)返回NO会涎,clsName也會(huì)為null
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
// 當(dāng)上面返回NO,進(jìn)行協(xié)議的捕獲
NSMutableArray *protocols = nil;
while ([scanner scanString:@"<" intoString:NULL]) {
NSString* protocol = nil;
if ([scanner scanUpToString:@">" intoString: &protocol]) {
if (protocol.length) {
if (!protocols) protocols = [NSMutableArray new];
[protocols addObject:protocol];
}
}
[scanner scanString:@">" intoString:NULL];
}
_protocols = protocols;
}
objc_msgSend
YYModel中設(shè)置和獲取屬性的值均經(jīng)過(guò)objc_msgSend
函數(shù)實(shí)現(xiàn)瑞凑,我們要知道末秃,所有OC的方法的調(diào)用(消息的接收)在編譯之后均是通過(guò)調(diào)用objc_msgSend
函數(shù)完成的。關(guān)于objc_msgSend
的使用可參考鏈接籽御。要想驗(yàn)證編譯后的OC代碼练慕,可以在終端下使用Clang
命令進(jìn)行驗(yàn)證。具體可以參考該文章.
實(shí)現(xiàn)解析
重要的類
- _YYModelMeta —————— 完成Model屬性技掏、方法铃将、實(shí)例變量的解析,并生成與數(shù)據(jù)源相對(duì)應(yīng)的字典映射哑梳;
- YYClassInfo —————— 存儲(chǔ)Model解析后的各種信息劲阎;
- YYClassMethodInfo —————— 存儲(chǔ)方法的信息;
- YYClassIvarInfo —————— 存儲(chǔ)實(shí)例變量的信息鸠真;
- YYClassPropertyInfo —————— 存儲(chǔ)屬性的信息悯仙;
- _YYModelPropertyMeta —————— 該類包含了屬性的信息和設(shè)置屬性時(shí)所需要的信息龄毡,有上述解析得到的信息和自定義的信息合成,用于第二步中的映射關(guān)系生成锡垄。
實(shí)現(xiàn)步驟
1沦零、解析實(shí)體信息
自定義的Model類調(diào)用modelWithDictionary:
、modelWithJSON:
進(jìn)行Model的初始化货岭,接下來(lái)_YYModelMeta
類將會(huì)為我們完成解析的工作路操。解析的目的是獲取Model類的方法、屬性千贯、實(shí)例變量信息屯仗,這些信息將保存在YYClassInfo
中。
在解析之初會(huì)首先檢查是否存在緩存搔谴,如果有緩存則直接返回緩存的_YYModelMeta
對(duì)象祭钉。通常情況下,每一個(gè)類的屬性己沛、實(shí)例變量的解析只會(huì)進(jìn)行一次,成功解析一次后的數(shù)據(jù)將會(huì)被緩存起來(lái)距境,只有當(dāng)設(shè)置了YYClassInfo
的_needUpdate
才會(huì)進(jìn)行新的解析申尼,也就是進(jìn)行動(dòng)態(tài)的添加屬性、修改方法后需要更新垫桂。緩存數(shù)據(jù)保存在一個(gè)靜態(tài)的CFMutableDictionaryRef
字典當(dāng)中师幕,并通過(guò)dispatch_semaphore_t
信號(hào)量來(lái)確保字典讀取的線程安全性。
沒(méi)有緩存诬滩,則創(chuàng)建_YYModelMeta
和YYClassInfo
對(duì)象霹粥,其中YYClassInfo
也加入了緩存處理,個(gè)人覺(jué)得這里是沒(méi)有必要的疼鸟,因?yàn)?code>_YYModelMeta已經(jīng)有屬性引用了YYClassInfo
后控。
YYClassInfo
的_update
會(huì)為我們完成具體的解析,該方法依次對(duì)方法空镜、屬性浩淘、實(shí)例變量進(jìn)行了解析,個(gè)人發(fā)現(xiàn)方法吴攒、實(shí)例變量解析的信息并未用到张抄。所以這里著重說(shuō)一下屬性的解析,通過(guò)class_copyPropertyList
獲取到屬性列表洼怔,并為每一個(gè)屬性生成一個(gè)YYClassPropertyInfo
對(duì)象署惯,該對(duì)象會(huì)保存在YYClassInfo
的_propertyInfos
數(shù)組中。生成的YYClassPropertyInfo
對(duì)象主要包含了屬性名镣隶、setter极谊、getter诡右、屬性數(shù)據(jù)類型等,這些信息都有對(duì)應(yīng)的runtime方法怀酷。其中核心在于利用類型編碼完成屬性數(shù)據(jù)類型的解析稻爬。
解析屬性的核心代碼:
/// 1.獲取Model類的屬性列表
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
if (properties) {
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
// 將生成的YYClassPropertyInfo對(duì)象保存在該數(shù)組中
_propertyInfos = propertyInfos;
for (unsigned int i = 0; i < propertyCount; i++) {
YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
if (info.name) propertyInfos[info.name] = info;
}
// 記得釋放內(nèi)存
free(properties);
}
/// 2.初始化YYClassPropertyInfo對(duì)象
- (instancetype)initWithProperty:(objc_property_t)property {
if (!property) return nil;
self = [super init];
_property = property;
// 獲取屬性的名稱
const char *name = property_getName(property);
if (name) {
// 將屬性名稱有C字符串轉(zhuǎn)化為NSString
_name = [NSString stringWithUTF8String:name];
}
YYEncodingType type = 0;
unsigned int attrCount;
// 獲取屬性對(duì)應(yīng)的所有描述,即objc_property_attribute_t的結(jié)構(gòu)體數(shù)組蜕依,包括原子性桅锄、數(shù)據(jù)類型、內(nèi)存語(yǔ)義等
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int i = 0; i < attrCount; i++) {
// objc_property_attribute_t的name為C字符串样眠,name[0]表示獲取第一個(gè)字符
switch (attrs[i].name[0]) {
case 'T': { // 這里將解析數(shù)據(jù)的類型友瘤,比如NSString、BOOL檐束、NSArray等
if (attrs[i].value) {
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
// 剛方法用于解析具體的數(shù)據(jù)類型
type = YYEncodingGetType(attrs[i].value);
// 只有當(dāng)數(shù)據(jù)類型為NSObject類辫秧,包括采納了協(xié)議的屬性,才需要如下進(jìn)一步的解析
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
// 將解析到的數(shù)據(jù)類型的類保存在_cls屬性中
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
// 對(duì)于采納協(xié)議的屬性的解析
NSMutableArray *protocols = nil;
while ([scanner scanString:@"<" intoString:NULL]) {
NSString* protocol = nil;
if ([scanner scanUpToString:@">" intoString: &protocol]) {
if (protocol.length) {
if (!protocols) protocols = [NSMutableArray new];
[protocols addObject:protocol];
}
}
[scanner scanString:@">" intoString:NULL];
}
_protocols = protocols;
}
}
} break;
case 'V': { // Instance variable
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
}
} break;
case 'R': {
type |= YYEncodingTypePropertyReadonly;
} break;
case 'C': {
type |= YYEncodingTypePropertyCopy;
} break;
case '&': {
type |= YYEncodingTypePropertyRetain;
} break;
case 'N': {
type |= YYEncodingTypePropertyNonatomic;
} break;
case 'D': {
type |= YYEncodingTypePropertyDynamic;
} break;
case 'W': {
type |= YYEncodingTypePropertyWeak;
} break;
case 'G': { // 獲取自定義的getter方法
type |= YYEncodingTypePropertyCustomGetter;
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} break;
case 'S': { // 獲取自定義的setter方法
type |= YYEncodingTypePropertyCustomSetter;
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} // break; commented for code coverage in next line
default: break;
}
}
if (attrs) {
free(attrs);
attrs = NULL;
}
// 保存獲取的type以及驗(yàn)證_setter被丧、_getter是否已經(jīng)獲取到盟戏,這三個(gè)要素在設(shè)置屬性值時(shí)將會(huì)被用到
_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;
}
2、生成映射關(guān)系
通過(guò)生成的YYClassInfo
來(lái)建立與數(shù)據(jù)源之間的映射關(guān)系甥桂,也就是生成了一個(gè)以數(shù)據(jù)源字典的key為key柿究,以_YYModelPropertyMeta
對(duì)象為值的字典_mapper
,核心類為_YYModelPropertyMeta
黄选,該類包含了屬性的信息和用戶自定義的信息蝇摸,例如屬性的setter、getter办陷、對(duì)應(yīng)數(shù)據(jù)源的key貌夕、子類容器等。
在生成_mapper
的過(guò)程中包括白名單民镜、黑名單啡专、容器類、自定義數(shù)據(jù)源的key與屬性名映射關(guān)系的處理殃恒,具體可參見(jiàn)_YYModelMeta
類的initWithClass:
方法植旧。
3、設(shè)置屬性值
上述兩個(gè)步驟均是在_YYModelMeta
中完成的离唐,第三部回到NSObject+YYModel
分類中病附,用modelSetWithDictionary:
方法完成屬性值的設(shè)置。
核心代碼:
/// 使用下面的函數(shù)亥鬓,遍歷字典并通過(guò)ModelSetWithDictionaryFunction函數(shù)完成Model屬性值的設(shè)置
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
/// context為ModelSetContext結(jié)構(gòu)體
typedef struct {
void *modelMeta; /// 為步驟1完沪、2完成后生成的_YYModelMeta對(duì)象
void *model; /// 要生成的實(shí)體類,相當(dāng)于id類型
void *dictionary; /// 數(shù)據(jù)源
} ModelSetContext;
/// 屬性值的設(shè)置最終通過(guò)objc_msgSend函數(shù)實(shí)現(xiàn),例如下列對(duì)NSString類型的屬性進(jìn)行設(shè)置
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == YYEncodingTypeNSString) {
((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);
}
}
問(wèn)題
- 為何要做metaclass的處理和判斷覆积?
- 獲取方法列表和實(shí)例變量列表的意義在哪听皿?