一、Class 結(jié)構(gòu)

1.1 objc_class(類)
上圖是 Class 的總體結(jié)構(gòu)圖,其中 OC 中的 Class 直接轉(zhuǎn)換為 C++ 中的對(duì)應(yīng)的結(jié)構(gòu)體objc_class,objc_class中主要包含:
-
isa指針:對(duì)象的isa指向類,類的isa指向元類,元類的isa指向根元類,根元類的isa指向自身。 -
supreclass指針:指向父類。 -
cache方法緩存列表,下面第二小節(jié)會(huì)重點(diǎn)介紹。 -
bits包含著具體的類信息。
1.2 cache_t (方法緩存)

cache_t 主要用來(lái)緩存方法,內(nèi)部使用散列表(也稱哈希表)來(lái)緩存曾經(jīng)調(diào)用過(guò)的方法,以SEL作為 key ,以IMP作為Value ,通過(guò)空間換時(shí)間的思想提高方法查找速度。每次調(diào)用方法前,首先去cache_t中查找通過(guò)散列表特性查找是否有該方法。存在則直接調(diào)用,否則去class_rw_t中的methods 遍歷查找該方法,調(diào)用并將該方法緩存到cache_t中,以便提高下次調(diào)用效率。
1.3 class_rw_t (類信息)
bits & FAST_DATA_MASK 可獲取類信息,對(duì)應(yīng)的類型為class_rw_t(rw表示read、write,即可讀可寫)。
-
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一維數(shù)組,是只讀的( ro 即read only),僅包含了類的初始內(nèi)容,未包含分類的信息。當(dāng)首次加載類的時(shí)候,會(huì)將class_rw_t中的方法列表加載到二維數(shù)組methods中,放在數(shù)組末尾,具體可以查看Category底層分析。 -
class_rw_t里面的methods、properties、protocols是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容以及分類的內(nèi)容。
1.4 method_t(方法結(jié)構(gòu)體)
method_t是對(duì)方法或函數(shù)的封裝。主要由三部分組成:
- IMP代表函數(shù)的具體實(shí)現(xiàn)
- SEL代表方法或函數(shù)名,也做選擇器
- types包含了函數(shù)返回值以及參數(shù)編碼的字符串,蘋果官方文檔搜索
Type Encoding可查看相關(guān)信息。如[self test]中,test 方法對(duì)應(yīng)的types值為v16@0:8,v表示void,@表示id,:表示SEL, 因?yàn)镺C方法調(diào)用底層會(huì)默認(rèn)傳兩個(gè)參數(shù),一個(gè)是id類型的self,一個(gè)是SEL類型的_cmd。另外,16 表示參數(shù)的總長(zhǎng)度,0 和 8 分別表示第一個(gè)參數(shù)和第二個(gè)參數(shù)的偏移值。
iOS中提供了一個(gè)叫做@encode的指令,可以將具體的類型表示成字符串編碼。如NSLog(@"%s",@encode(id)) 輸出結(jié)果為@。
二、消息發(fā)送
OC 中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為 objc_msgSend 函數(shù)的調(diào)用,該函數(shù)默認(rèn)接收兩個(gè)參數(shù):一個(gè)是id類型的消息接收者,另一個(gè)是SEL類型的_cmd。objc_msgSend的執(zhí)行流程可以分為三個(gè)階段,如果三個(gè)階段都沒有找到合適的方法進(jìn)行調(diào)用,就會(huì)報(bào)一個(gè)經(jīng)典錯(cuò)誤unrecognized selector sent to instance。
- 消息發(fā)送階段
- 動(dòng)態(tài)方法解析階段
- 消息轉(zhuǎn)發(fā)階段

- 1、消息發(fā)送階段首先判斷消息接收者是否為 nil ,為 nil 直接退出;否則,去消息接收者的cache中尋找是否存在發(fā)方法。
- 2、消息接收者的cache中存在直接調(diào)用,不存在則去接收者的類信息
class_rw_t中查找,查找到放入到消息接收者的cache中;否則,去消息接收者父類cache中查找; - 3、消息接收者父類cache中存在該方法,直接調(diào)用;不存在則去接收者的父類類信息
class_rw_t中查找,查找到放入到消息接收者父類的cache中(從圖中看應(yīng)該是放入消息接受者的cache中,和此處矛盾);否則,去消息接收者父類的父類cache中查找;即循環(huán)執(zhí)行第三步,也即上圖紅色框圈入的部分。 - 4、如果一直找到最基層的父類中,依然沒有找到方法,則會(huì)進(jìn)入動(dòng)態(tài)方法解析階段。
子類沒有實(shí)現(xiàn)方法會(huì)調(diào)用父類的方法,并且將父類方法加入到子類自己的cache 里。?????
三、動(dòng)態(tài)方法解析

如果可以找到方法的實(shí)現(xiàn),就進(jìn)入消息轉(zhuǎn)發(fā)階段;否則,調(diào)用+resolveInstanceMethod:和+resolveClassMethod:,在上述兩個(gè)方法內(nèi)動(dòng)態(tài)添加方法,防止崩潰,之后返回YES,重新返回消息發(fā)送階段;如果沒有動(dòng)態(tài)添加方法,直接返回YES 或 NO,依然無(wú)法找到方法實(shí)現(xiàn),進(jìn)而發(fā)生奔潰。注意:上述兩個(gè)方法的返回值都是 BOOL 類型,返回值只是標(biāo)記是否動(dòng)態(tài)解析方法,無(wú)論返回 YES 還是 NO,對(duì)消息執(zhí)行流程無(wú)任何影響。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
// 獲取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (void)other{
NSLog(@"%s", __func__);
}
四、消息轉(zhuǎn)發(fā)階段

所謂的消息轉(zhuǎn)發(fā)是指:自身無(wú)法處理消息,轉(zhuǎn)發(fā)給他人來(lái)處理。當(dāng)方法解析階段找到對(duì)應(yīng)方法實(shí)現(xiàn),就會(huì)來(lái)到消息轉(zhuǎn)發(fā)階段。消息轉(zhuǎn)發(fā)階段有三次攔截消息的機(jī)會(huì),即下面三個(gè)方法,三個(gè)方法都有對(duì)應(yīng)的對(duì)象方法和類方法。
forwardingTargetForSelectormethodSignatureForSelectorforwardInvocation
4.1 forwardingTargetForSelector(返回方法處理者)
@interface Person : NSObject
- (void)test;
@end
@implementation Person
//如果不實(shí)現(xiàn)該方法默認(rèn)返回nil,則會(huì)進(jìn)一步調(diào)用`methodSignatureForSelector `方法;否則,由該方法返回的對(duì)象處理消息。
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
//如果返回nil,則直接調(diào)用methodSignatureForSelector方法
return [[Cat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Person *person = [[Person alloc] init];
[person test];
如果不實(shí)現(xiàn)forwardingTargetForSelector 方法默認(rèn)返回nil 或直接在該方法中返回 nil ,則會(huì)進(jìn)一步調(diào)用methodSignatureForSelector方法;否則,由該方法返回的對(duì)象處理消息,即傳入返回的對(duì)象,直接調(diào)用objc_msgSend(返回值, SEL)方法。上面的Person類中沒有test方法的實(shí)現(xiàn),forwardingTargetForSelector: 方法中返回 [[Cat alloc] init] 對(duì)象處理。
4.2 methodSignatureForSelector(返回方法簽名)
如果方法methodSignatureForSelector返回的方法簽名為 nil,則直接崩潰;如果返回不為 nil ,則說(shuō)明想處理消息,則進(jìn)一步調(diào)用forwardInvocation方法。返回的方法簽名會(huì)傳遞到forwardInvocation方法中的NSInvocation對(duì)象中。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
//這里的方法參數(shù)、返回值和`forwardInvocation`中的NSInvocation對(duì)應(yīng)
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
4.3 forwardInvocation(方法調(diào)用)
當(dāng)來(lái)到該方法后,可以在這里做任何事情。其中傳入的參數(shù)anInvocation對(duì)象封裝的是一個(gè)方法的調(diào)用,包含了方法調(diào)用者(anInvocation.target)、方法名(anInvocation.selector)、方法參數(shù)([anInvocation getArgument:NULL atIndex:0])。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"1123");
}
4.4 doesNotRecognizeSelector,發(fā)生崩潰
如果在為消息解析階段找不到方法實(shí)現(xiàn),且上述三個(gè)方法沒有做任何處理,會(huì)直接調(diào)用doesNotRecognizeSelector方法 ,最終發(fā)生崩潰。
4.5 消息轉(zhuǎn)發(fā)實(shí)際開發(fā)應(yīng)用(防止找不到方法崩潰)
消息轉(zhuǎn)發(fā)機(jī)制筆者在實(shí)際開發(fā)中有用到,主要是用來(lái)處理方法找不到,發(fā)生崩潰問(wèn)題。思路主要是在+load方法中,交叉替換forwardingTargetForSelector方法,動(dòng)態(tài)創(chuàng)建新的方法處理對(duì)象,并在該對(duì)象中動(dòng)態(tài)添加方法。具體實(shí)現(xiàn)如下:
@implementation NSObject (Exception)
+ (void)load {
//防止外部手動(dòng)調(diào)用load方法,固load方法中最好都要寫上dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass("NSObject") swizzleMethod:@selector(forwardingTargetForSelector:) swizzledSelector:@selector(replace_forwardingTargetForSelector:)];
}
});
}
- (id)replace_forwardingTargetForSelector:(SEL)aSelector{
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
if ([self respondsToSelector:aSelector] || signature) {
return [self replace_forwardingTargetForSelector:aSelector];
}
return [NSObject createFakeForwardTargetObject:self selector:aSelector];
}
+ (id)createFakeForwardTargetObject:(id)aTarget selector:(SEL)aSelector{
if ([[NSString string] respondsToSelector:aSelector]) {
NSString *szTarget = nil;
if ([aTarget isKindOfClass:[NSNumber class]]) {
szTarget = [NSString stringWithFormat:@"%@", aTarget];
}
if (szTarget) {
return szTarget;
}
}
FakeForwardTargetObject *fakeTaget = [[FakeForwardTargetObject alloc] initWithSelector:aSelector];
return fakeTaget;
}
@end
@implementation FakeForwardTargetObject
- (instancetype)initWithSelector:(SEL)aSelector{
if (self = [super init]) {
if(class_addMethod([self class], aSelector, (IMP)fakeIMP, NULL)) {
MCLog(@"add Fake Selector:[instance %@]", NSStringFromSelector(aSelector));
NSString *string = [NSString stringWithFormat:@"[%s:%d行]",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],__LINE__];
showExceptionAlert(string);
}
}
return self;
}
@end
id fakeIMP(id sender,SEL sel,...){
return nil;
}