在iOS底層原理12:動態(tài)方法決議中探究了動態(tài)方法決議。在動態(tài)決議之后,通過日志輔助功能認(rèn)識到forwardingTargetForSelector和methodSignatureForSelector方法,也就是消息發(fā)送的最后一個流程消息轉(zhuǎn)發(fā)
準(zhǔn)備工作
消息轉(zhuǎn)發(fā)
消息發(fā)送在經(jīng)過動態(tài)方法決議后,仍然沒有查找到正真的方法實現(xiàn),此時進(jìn)入消息轉(zhuǎn)發(fā)流程。轉(zhuǎn)發(fā)流程分兩步快速轉(zhuǎn)發(fā)和慢速轉(zhuǎn)發(fā)
快速轉(zhuǎn)發(fā)流程
通過日志輔助發(fā)現(xiàn),在崩潰之前會執(zhí)行forwardingTargetForSelector方法,即消息快速流程
forwardingTargetForSelector方法探究
打開Xcode,通過快捷鍵command + shift + 0打開開發(fā)者文檔,然后搜索forwardingTargetForSelector,結(jié)果如下圖

- 根據(jù)開發(fā)者文檔的描述,
forwardingTargetForSelector返回了一個重定向?qū)ο?/code>,這個對象來響應(yīng)未實現(xiàn)的方法。
代碼驗證
- 新建一個
iOS工程,創(chuàng)建兩個類HTPerson和HTCommon-
HTPerson類 只有實例方法sayHello、類方法sayBye的聲明,無實現(xiàn) -
HTCommon類 實現(xiàn)了這兩個方法
-

- 在
HTPerson中添加forwardingTargetForSelector方法,代碼如下
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [[HTCommon alloc] init];
} else if (aSelector == @selector(sayBye)) {
return [HTCommon class];
}
return [NSObject alloc];
}
- 運行程序,對象方法
sayHello已經(jīng)成功調(diào)用了,但是類方法依然會導(dǎo)致崩潰

【問題】如何通過消息轉(zhuǎn)發(fā)快速流程,來處理類方法呢?這里猜測需要通過+ (id)forwardingTargetForSelector:(SEL)aSelector {} 來處理類方法
- 繼續(xù)修改
forwardingTargetForSelector方法,代碼如下
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [[HTCommon alloc] init];
}
return [NSObject alloc];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayBye)) {
return [HTCommon class];
}
return [NSObject class];
}

慢速轉(zhuǎn)發(fā)流程
如果通過快速轉(zhuǎn)發(fā)流程forwardingTargetForSelector還是找不到方法實現(xiàn),接下來蘋果還給了我們一次機(jī)會,即慢速轉(zhuǎn)發(fā)流程
- 慢速轉(zhuǎn)發(fā)流程
methodSignatureForSelector,查看文檔如下:

-
methodSignatureForSelector方法返回的是NSMethodSignature對象,該對象包含由給定選擇器標(biāo)識的方法的描述。methodSignatureForSelector一般和forwardInvocation搭配使用,如果methodSignatureForSelector方法返回的是一個nil就不會調(diào)用forwardInvocation
代碼驗證
#pragma mark- 處理對象方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(sayHello)) {
HTCommon *common = [[HTCommon alloc] init];
anInvocation.target = common;
return [anInvocation invoke];
}
return [super forwardInvocation:anInvocation];
}
#pragma mark- 處理類方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(sayBye)) {
return [NSMethodSignature signatureWithObjCTypes:"v:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(anInvocation.selector));
}
如果methodSignatureForSelector的返回值是NSMethodSignature對象,則會調(diào)用forwardInvocation方法對anInvocation事務(wù)進(jìn)行處理,如果不處理也不會報錯
消息轉(zhuǎn)發(fā)總結(jié)
消息轉(zhuǎn)發(fā)的處理主要分為兩部分:
- 【
快速轉(zhuǎn)發(fā)】當(dāng)慢速查找,以及動態(tài)方法決議均沒有找到實現(xiàn)時,進(jìn)行消息轉(zhuǎn)發(fā),首先是進(jìn)行快速消息轉(zhuǎn)發(fā),即走到forwardingTargetForSelector方法- 如果返回
消息接收者,在消息接收者中還是沒有找到,則進(jìn)入另一個方法的查找流程 - 如果返回
nil,則進(jìn)入慢速消息轉(zhuǎn)發(fā)
- 如果返回
- 【
慢速轉(zhuǎn)發(fā)】執(zhí)行到methodSignatureForSelector方法- 如果返回的方法簽名為
nil,則直接崩潰報錯 - 如果返回的方法簽名
不為nil,走到forwardInvocation方法中,對anInvocation事務(wù)進(jìn)行處理,如果不處理也不會報錯
- 如果返回的方法簽名為
方法調(diào)用流程

總結(jié)
至此,objc_msgSend發(fā)送消息的流程就分析完成了,我們可以得出整個方法調(diào)用的流程:
- 【
快速查找流程】:在類的緩存cache中查找指定方法的實現(xiàn) - 【
慢速查找流程】:如果緩存中沒有找到,則在類的方法列表中查找(二分查找),如果還是沒找到,則去父類鏈的緩存和方法列表中查找 - 【
動態(tài)方法決議】:如果慢速查找還是沒有找到時,第一次補救機(jī)會就是嘗試一次動態(tài)方法決議,即重寫resolveInstanceMethod/resolveClassMethod方法 - 【
消息轉(zhuǎn)發(fā)】:如果動態(tài)方法決議還是沒有找到,則進(jìn)行消息轉(zhuǎn)發(fā),消息轉(zhuǎn)發(fā)中有兩次補救機(jī)會:快速轉(zhuǎn)發(fā)+慢速轉(zhuǎn)發(fā) - 如果轉(zhuǎn)發(fā)之后也沒有,則程序直接報錯崩潰
unrecognized selector sent to instance
緩存cache快速查找流程 --> 慢速查找流程 --> 動態(tài)決議方法resolveInstanceMethod --> 快速轉(zhuǎn)發(fā)流程forwardingTargetForSelector --> 慢速轉(zhuǎn)發(fā)流程(methodSignatureForSelector) --> resolveInstanceMethod --> forwardInvocation --> 崩潰報錯
補充
hopper反匯編CoreFoundation系統(tǒng)庫
查看崩潰時的堆棧信息,調(diào)用了CoreFoundation系統(tǒng)庫的forwarding_prep_0 和 ___forwarding___方法,如下圖

下載CoreFoundation源碼,并沒有找到這兩個方法的實現(xiàn),說明這塊內(nèi)容蘋果并沒有對外提供,只是開源了部分CoreFoundation源碼
- 通過
image list獲取所有的鏡像文件列表,找到CoreFoundation庫的文件路徑

- 通過
objdump --macho --syms CoreFoundation | grep "forwarding"查看CoreFoundation庫的符號表,發(fā)現(xiàn)___forwarding_prep_0___和____forwarding___都是本地符號

forwarding_prep_0方法
全局搜索__forwarding_prep_0___,發(fā)現(xiàn)只有一個,且會調(diào)用__forwarding__

____forwarding___方法
- 快速轉(zhuǎn)發(fā)流程
- 如果
forwardingTargetForSelector方法沒有實現(xiàn),跳轉(zhuǎn)loc_115baf流程 - 如果
forwardingTargetForSelector方法的返回值是nil,跳轉(zhuǎn)loc_115baf流程
- 如果

-
慢速轉(zhuǎn)發(fā)流程
- 如果
methodSignatureForSelector沒有實現(xiàn)直接跳轉(zhuǎn)到loc_115f4a流程,最終會進(jìn)入loc_115fc5流程 - 如果
methodSignatureForSelector返回值等于nil跳轉(zhuǎn)到loc_115fc5流程 - 如果
methodSignatureForSelector返回了簽名信息的對象,則會調(diào)用_forwardStackInvocation:方法,最后會執(zhí)行forwardInvocation方法
image
- 如果
慢速流程如果沒有實現(xiàn)的話,則會進(jìn)入
doesNotRecognizeSelector:方法

-
doesNotRecognizeSelector主要就是對崩潰信息的處理,以及輸出報錯信息

