在前面兩篇文章objc_msgSend分析之快速查找和objc_msgSend分析之慢速查找中,分別分析了objc_msgSend的快速查找和慢速查找,在這兩種都沒找到方法實(shí)現(xiàn)的情況下,蘋果給了兩個(gè)建議
-
動(dòng)態(tài)方法決議:慢速查找流程未找到后,會(huì)執(zhí)行一次動(dòng)態(tài)方法決議 -
消息轉(zhuǎn)發(fā):如果動(dòng)態(tài)方法決議仍然沒有找到實(shí)現(xiàn),則進(jìn)行消息轉(zhuǎn)發(fā)
如果這兩個(gè)建議都沒有做任何操作,就會(huì)報(bào)我們?nèi)粘i_發(fā)中常見的方法未實(shí)現(xiàn)的崩潰報(bào)錯(cuò),其步驟如下
-
定義
LGPerson類,其中say666實(shí)例方法 和sayNB類方法均沒有實(shí)現(xiàn) -
main中 分別調(diào)用
LGPerson的實(shí)例方法say666和類方法sayNB,運(yùn)行程序,均會(huì)報(bào)錯(cuò),提示方法未實(shí)現(xiàn),如下所示- 調(diào)用實(shí)例方法say666的報(bào)錯(cuò)結(jié)果
實(shí)例方法報(bào)錯(cuò)- 調(diào)用類方法sayNB的報(bào)錯(cuò)結(jié)果
- image
方法未實(shí)現(xiàn)報(bào)錯(cuò)源碼
根據(jù)慢速查找的源碼,我們發(fā)現(xiàn),其報(bào)錯(cuò)最后都是走到__objc_msgForward_impcache方法,以下是報(bào)錯(cuò)流程的源碼
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
//??
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
- 匯編實(shí)現(xiàn)中查找
__objc_forward_handler,并沒有找到,在源碼中去掉一個(gè)下劃線進(jìn)行全局搜索_objc_forward_handler,有如下實(shí)現(xiàn),本質(zhì)是調(diào)用的objc_defaultForwardHandler方法
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
看著objc_defaultForwardHandler有沒有很眼熟,這就是我們在日常開發(fā)中最常見的錯(cuò)誤:沒有實(shí)現(xiàn)函數(shù),運(yùn)行程序,崩潰時(shí)報(bào)的錯(cuò)誤提示。
下面,我們來講講如何在崩潰前,如何操作,可以防止方法未實(shí)現(xiàn)的崩潰。
三次方法查找的挽救機(jī)會(huì)
根據(jù)蘋果的兩個(gè)建議,我們一共有三次挽救的機(jī)會(huì):
【第一次機(jī)會(huì)】動(dòng)態(tài)方法決議
-
消息轉(zhuǎn)發(fā)流程
- 【第二次機(jī)會(huì)】快速轉(zhuǎn)發(fā)
- 【第三次機(jī)會(huì)】慢速轉(zhuǎn)發(fā)
【第一次機(jī)會(huì)】動(dòng)態(tài)方法決議
在慢速查找流程未找到方法實(shí)現(xiàn)時(shí),首先會(huì)嘗試一次動(dòng)態(tài)方法決議,其源碼實(shí)現(xiàn)如下:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//對象 -- 類
if (! cls->isMetaClass()) { //類不是元類,調(diào)用對象的解析方法
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {//如果是元類,調(diào)用類的解析方法, 類 -- 元類
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
//為什么要有這行代碼? -- 類方法在元類中是對象方法,所以還是需要查詢元類中對象方法的動(dòng)態(tài)方法決議
if (!lookUpImpOrNil(inst, sel, cls)) { //如果沒有找到或者為空,在元類的對象方法解析方法中查找
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//如果方法解析中將其實(shí)現(xiàn)指向其他方法,則繼續(xù)走方法查找流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
主要分為以下幾步
- 判斷類是否是元類
- 如果是
類,執(zhí)行實(shí)例方法的動(dòng)態(tài)方法決議resolveInstanceMethod - 如果是
元類,執(zhí)行類方法的動(dòng)態(tài)方法決議resolveClassMethod,如果在元類中沒有找到或者為空,則在元類的實(shí)例方法的動(dòng)態(tài)方法決議resolveInstanceMethod中查找,主要是因?yàn)?code>類方法在元類中是實(shí)例方法,所以還需要查找元類中實(shí)例方法的動(dòng)態(tài)方法決議
- 如果是
- 如果
動(dòng)態(tài)方法決議中,將其實(shí)現(xiàn)指向了其他方法,則繼續(xù)查找指定的imp,即繼續(xù)慢速查找lookUpImpOrForward流程
其流程如下

實(shí)例方法
針對實(shí)例方法調(diào)用,在快速-慢速查找均沒有找到實(shí)例方法的實(shí)現(xiàn)時(shí),我們有一次挽救的機(jī)會(huì),即嘗試一次動(dòng)態(tài)方法決議,由于是實(shí)例方法,所以會(huì)走到resolveInstanceMethod方法,其源碼如下
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// look的是 resolveInstanceMethod --相當(dāng)于是發(fā)送消息前的容錯(cuò)處理
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel); //發(fā)送resolve_sel消息
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//查找say666
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
主要分為以下幾個(gè)步驟:
- 在
發(fā)送resolveInstanceMethod消息前,需要查找cls類中是否有該方法的實(shí)現(xiàn),即通過lookUpImpOrNil方法又會(huì)進(jìn)入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法- 如果沒有,則直接返回
- 如果有,則發(fā)送
resolveInstanceMethod消息
- 再次慢速查找實(shí)例方法的實(shí)現(xiàn),即通過
lookUpImpOrNil方法又會(huì)進(jìn)入lookUpImpOrForward慢速查找流程查找實(shí)例方法
崩潰修改
所以,針對實(shí)例方法say666未實(shí)現(xiàn)的報(bào)錯(cuò)崩潰,可以通過在類中重寫resolveInstanceMethod類方法,并將其指向其他方法的實(shí)現(xiàn),即在LGPerson中重寫resolveInstanceMethod類方法,將實(shí)例方法say666的實(shí)現(xiàn)指向sayMaster方法實(shí)現(xiàn),如下所示
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
//獲取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
//獲取sayMaster的實(shí)例方法
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
//獲取sayMaster的豐富簽名
const char *type = method_getTypeEncoding(sayMethod);
//將sel的實(shí)現(xiàn)指向sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
重新運(yùn)行,其打印結(jié)果如下
從結(jié)果中可以發(fā)現(xiàn),resolveInstanceMethod動(dòng)態(tài)決議方法中“來了”打印了兩次,這是為什么呢?通過堆棧信息可以看出
- 堆棧信息
【第一次動(dòng)態(tài)決議】第一次的“來了”是在查找
say666方法時(shí)會(huì)進(jìn)入動(dòng)態(tài)方法決議【第二次動(dòng)態(tài)決議】第二次“來了”是在慢速轉(zhuǎn)發(fā)流程中調(diào)用了
CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后,會(huì)再次進(jìn)入動(dòng)態(tài)決議
注:詳細(xì)的分析流程請看文末的問題探索
類方法
針對類方法,與實(shí)例方法類似,同樣可以通過重寫resolveClassMethod類方法來解決前文的崩潰問題,即在LGPerson類中重寫該方法,并將sayNB類方法的實(shí)現(xiàn)指向類方法lgClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
resolveClassMethod類方法的重寫需要注意一點(diǎn),傳入的cls不再是類,而是元類,可以通過objc_getMetaClass方法獲取類的元類,原因是因?yàn)?code>類方法在元類中是實(shí)例方法
優(yōu)化
上面的這種方式是單獨(dú)在每個(gè)類中重寫,有沒有更好的,一勞永逸的方法呢?其實(shí)通過方法慢速查找流程可以發(fā)現(xiàn)其查找路徑有兩條
實(shí)例方法:類 -- 父類 -- 根類 -- nil
類方法:元類 -- 根元類 -- 根類 -- nil
它們的共同點(diǎn)是如果前面沒找到,都會(huì)來到根類即NSObject中查找,所以我們是否可以將上述的兩個(gè)方法統(tǒng)一整合在一起呢?答案是可以的,可以通過NSObject添加分類的方式來實(shí)現(xiàn)統(tǒng)一處理,而且由于類方法的查找,在其繼承鏈,查找的也是實(shí)例方法,所以可以將實(shí)例方法 和 類方法的統(tǒng)一處理放在resolveInstanceMethod方法中,如下所示
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMethod);
return class_addMethod(self, sel, imp, type);
}else if (sel == @selector(sayNB)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
這種方式的實(shí)現(xiàn),正好與源碼中針對類方法的處理邏輯是一致的,即完美闡述為什么調(diào)用了類方法動(dòng)態(tài)方法決議,還要調(diào)用對象方法動(dòng)態(tài)方法決議,其根本原因還是類方法在元類中的實(shí)例方法。
當(dāng)然,上面這種寫法還是會(huì)有其他的問題,比如系統(tǒng)方法也會(huì)被更改,針對這一點(diǎn),是可以優(yōu)化的,即我們可以針對自定義類中方法統(tǒng)一方法名的前綴,根據(jù)前綴來判斷是否是自定義方法,然后統(tǒng)一處理自定義方法,例如可以在崩潰前pop到首頁,主要是用于app線上防崩潰的處理,提升用戶的體驗(yàn)。
消息轉(zhuǎn)發(fā)流程
在慢速查找的流程中,我們了解到,如果快速+慢速?zèng)]有找到方法實(shí)現(xiàn),動(dòng)態(tài)方法決議也不行,就使用消息轉(zhuǎn)發(fā),但是,我們找遍了源碼也沒有發(fā)現(xiàn)消息轉(zhuǎn)發(fā)的相關(guān)源碼,可以通過以下方式來了解,方法調(diào)用崩潰前都走了哪些方法
通過
instrumentObjcMessageSends方式打印發(fā)送消息的日志通過
hopper/IDA反編譯
通過instrumentObjcMessageSends
通過lookUpImpOrForward --> log_and_fill_cache --> logMessageSend,在logMessageSend源碼下方找到instrumentObjcMessageSends的源碼實(shí)現(xiàn),所以,在main中調(diào)用instrumentObjcMessageSends打印方法調(diào)用的日志信息,有以下兩點(diǎn)準(zhǔn)備工作
1、打開 objcMsgLogEnabled 開關(guān),即調(diào)用instrumentObjcMessageSends方法時(shí),傳入YES
2、在main中通過extern 聲明instrumentObjcMessageSends方法
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
通過logMessageSend源碼,了解到消息發(fā)送打印信息存儲在/tmp/msgSends 目錄,如下所示

運(yùn)行代碼,并前往
/tmp/msgSends 目錄,發(fā)現(xiàn)有msgSends開頭的日志文件,打開發(fā)現(xiàn)在崩潰前,執(zhí)行了以下方法
兩次
動(dòng)態(tài)方法決議:resolveInstanceMethod方法兩次
消息快速轉(zhuǎn)發(fā):forwardingTargetForSelector方法-
兩次
消息慢速轉(zhuǎn)發(fā):methodSignatureForSelector+resolveInvocation消息發(fā)送日志詳情
通過hopper/IDA反編譯
Hopper和IDA是一個(gè)可以幫助我們靜態(tài)分析可視性文件的工具,可以將可執(zhí)行文件反匯編成偽代碼、控制流程圖等,下面以Hopper為例(注:hopper高級版本是一款收費(fèi)軟件,針對比較簡單的反匯編需求來說,demo版本足夠使用了)
-
運(yùn)行程序崩潰,查看堆棧信息
發(fā)現(xiàn)
___forwarding___來自CoreFoundation
通過image list,讀取整個(gè)鏡像文件,然后搜索CoreFoundation,查看其可執(zhí)行文件的路徑
-
通過文件路徑,找到CoreFoundation的可執(zhí)行文件
-
-
打開hopper,選擇Try the Demo,然后將上一步的可執(zhí)行文件拖入hopper進(jìn)行反匯編,選擇x86(64 bits)
-
- hopper選擇Demo版本
- hopper反匯編
以下是反匯編后的界面,主要使用上面的三個(gè)功能,分別是 匯編、流程圖、偽代碼

通過左側(cè)的搜索框搜索
__forwarding_prep_0___,然后選擇偽代碼
-
以下是
__forwarding_prep_0___的匯編偽代碼,跳轉(zhuǎn)至___forwarding___
以下是___forwarding___的偽代碼實(shí)現(xiàn),首先是查看是否實(shí)現(xiàn)forwardingTargetForSelector方法,如果沒有響應(yīng),跳轉(zhuǎn)至loc_6459b即快速轉(zhuǎn)發(fā)沒有響應(yīng),進(jìn)入慢速轉(zhuǎn)發(fā)流程,
跳轉(zhuǎn)至
loc_6459b,在其下方判斷是否響應(yīng)methodSignatureForSelector方法,如果
沒有響應(yīng),跳轉(zhuǎn)至loc_6490b,則直接報(bào)錯(cuò)如果獲取
methodSignatureForSelector的方法簽名為nil,也是直接報(bào)錯(cuò)-
所以,通過上面兩種查找方式可以驗(yàn)證,消息轉(zhuǎn)發(fā)的方法有3個(gè)
-
【快速轉(zhuǎn)發(fā)】
forwardingTargetForSelector-
【慢速轉(zhuǎn)發(fā)】
methodSignatureForSelectorforwardInvocation
所以,綜上所述,消息轉(zhuǎn)發(fā)整體的流程如下
- image
消息轉(zhuǎn)發(fā)的處理主要分為兩部分:
-
【快速轉(zhuǎn)發(fā)】當(dāng)慢速查找,以及動(dòng)態(tài)方法決議均沒有找到實(shí)現(xiàn)時(shí),進(jìn)行消息轉(zhuǎn)發(fā),首先是進(jìn)行
快速消息轉(zhuǎn)發(fā),即走到forwardingTargetForSelector方法如果返回
消息接收者,在消息接收者中還是沒有找到,則進(jìn)入另一個(gè)方法的查找流程如果返回
nil,則進(jìn)入慢速消息轉(zhuǎn)發(fā)
-
【慢速轉(zhuǎn)發(fā)】執(zhí)行到
methodSignatureForSelector方法如果返回的方法簽名為
nil,則直接崩潰報(bào)錯(cuò)如果返回的方法簽名
不為nil,走到forwardInvocation方法中,對invocation事務(wù)進(jìn)行處理,如果不處理也不會(huì)報(bào)錯(cuò)
第二次機(jī)會(huì)】快速轉(zhuǎn)發(fā)
針對前文的崩潰問題,如果動(dòng)態(tài)方法決議也沒有找到實(shí)現(xiàn),則需要在LGPerson中重寫forwardingTargetForSelector方法,將LGPerson的實(shí)例方法的接收者指定為LGStudent的對象(LGStudent類中有say666的具體實(shí)現(xiàn)),如下所示
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
//將消息的接收者指定為LGStudent,在LGStudent中查找say666的實(shí)現(xiàn)
return [LGStudent alloc];
}
執(zhí)行結(jié)果如下

也可以直接不指定消息接收者,
直接調(diào)用父類的該方法,如果還是沒有找到,則直接報(bào)錯(cuò)

【第三次機(jī)會(huì)】慢速轉(zhuǎn)發(fā)
針對第二次機(jī)會(huì)即快速轉(zhuǎn)發(fā)中還是沒有找到,則進(jìn)入最后的一次挽救機(jī)會(huì),即在LGPerson中重寫methodSignatureForSelector,如下所示
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
}
打印結(jié)果如下,發(fā)現(xiàn)forwardInvocation方法中不對invocation進(jìn)行處理,也不會(huì)崩潰報(bào)錯(cuò)

也可以處理invocation事務(wù),如下所示,修改invocation的target為[LGStudent alloc],調(diào)用 [anInvocation invoke] 觸發(fā) 即LGPerson類的say666實(shí)例方法的調(diào)用會(huì)調(diào)用LGStudent的say666方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
anInvocation.target = [LGStudent alloc];
[anInvocation invoke];
}
打印結(jié)果如下

所以,由上述可知,無論在forwardInvocation方法中是否處理invocation事務(wù),程序都不會(huì)崩潰。
“動(dòng)態(tài)方法決議為什么執(zhí)行兩次?” 問題探索
在前文中提及了動(dòng)態(tài)方法決議方法執(zhí)行了兩次,有以下兩種分析方式
啟用上帝視角的探索
在慢速查找流程中,我們了解到resolveInstanceMethod方法的執(zhí)行是通過lookUpImpOrForward --> resolveMethod_locked --> resolveInstanceMethod來到resolveInstanceMethod源碼,在源碼中通過發(fā)送resolve_sel消息觸發(fā),如下所示

所以可以在resolveInstanceMethod方法中IMP imp = lookUpImpOrNil(inst, sel, cls);處加一個(gè)斷點(diǎn),通過bt打印堆棧信息來看到底發(fā)生了什么
-
在
resolveInstanceMethod方法中IMP imp = lookUpImpOrNil(inst, sel, cls);處加一個(gè)斷點(diǎn),運(yùn)行程序,直到第一次“來了”,通過bt查看第一次動(dòng)態(tài)方法決議的堆棧信息,此時(shí)的sel是say666第一次動(dòng)態(tài)方法決議堆棧信息
繼續(xù)往下執(zhí)行,直到第二次“來了”打印,查看堆棧信息,在第二次中,我們可以看到是通過CoreFoundation的-[NSObject(NSObject) methodSignatureForSelector:]方法,然后通過class_getInstanceMethod再次進(jìn)入動(dòng)態(tài)方法決議,

通過上一步的堆棧信息,我們需要去看看CoreFoundation中到底做了什么?通過Hopper反匯編CoreFoundation的可執(zhí)行文件,查看methodSignatureForSelector方法的偽代碼

通過methodSignatureForSelector偽代碼進(jìn)入___methodDescriptionForSelector的實(shí)現(xiàn)

進(jìn)入 ___methodDescriptionForSelector的偽代碼實(shí)現(xiàn),結(jié)合匯編的堆棧打印,可以看到,在___methodDescriptionForSelector這個(gè)方法中調(diào)用了objc4-781的class_getInstanceMethod

在objc中的源碼中搜索class_getInstanceMethod,其源碼實(shí)現(xiàn)如下所示

這一點(diǎn)可以通過代碼調(diào)試來驗(yàn)證,如下所示,在class_getInstanceMethod方法處加一個(gè)斷點(diǎn),在執(zhí)行了methodSignatureForSelector方法后,返回了簽名,說明方法簽名是生效的,蘋果在走到invocation之前,給了開發(fā)者一次機(jī)會(huì)再去查詢,所以走到class_getInstanceMethod這里,又去走了一遍方法查詢say666,然后會(huì)再次走到動(dòng)態(tài)方法決議

所以,上述的分析也印證了前文中resolveInstanceMethod方法執(zhí)行了兩次的原因
無上帝視角的探索
如果在沒有上帝視角的情況下,我們也可以通過代碼來推導(dǎo)在哪里再次調(diào)用了動(dòng)態(tài)方法決議
-
LGPerson中重寫
resolveInstanceMethod方法,并加上class_addMethod操作即賦值IMP,此時(shí)resolveInstanceMethod會(huì)走兩次嗎?
去掉resolveInstanceMethod方法中的賦值IMP,在LGPerson類中重寫forwardingTargetForSelector方法,并指定返回值為[LGStudent alloc],重新運(yùn)行,如果resolveInstanceMethod打印了兩次,說明是在forwardingTargetForSelector方法之前執(zhí)行了 動(dòng)態(tài)方法決議,反之,在forwardingTargetForSelector方法之后

-
【結(jié)論】:發(fā)現(xiàn)
resolveInstanceMethod中的打印還是只打印了一次,數(shù)排名第二次動(dòng)態(tài)方法決議 在forwardingTargetForSelector方法后* 在LGPerson中重寫methodSignatureForSelector和forwardInvocation,運(yùn)行 【結(jié)論】:
第二次動(dòng)態(tài)方法決議在methodSignatureForSelector和forwardInvocation方法之間
第二種分析同樣可以論證前文中resolveInstanceMethod執(zhí)行了兩次的原因
經(jīng)過上面的論證,我們了解到其實(shí)在慢速小子轉(zhuǎn)發(fā)流程中,在methodSignatureForSelector 和 forwardInvocation方法之間還有一次動(dòng)態(tài)方法決議,即蘋果再次給的一個(gè)機(jī)會(huì),如下圖所示

總結(jié)
到目前為止,objc_msgSend發(fā)送消息的流程就分析完成了,在這里簡單總結(jié)下
【快速查找流程】首先,在類的緩存cache中查找指定方法的實(shí)現(xiàn)【慢速查找流程】如果緩存中沒有找到,則在類的方法列表中查找,如果還是沒找到,則去父類鏈的緩存和方法列表中查找【動(dòng)態(tài)方法決議】如果慢速查找還是沒有找到時(shí),第一次補(bǔ)救機(jī)會(huì)就是嘗試一次動(dòng)態(tài)方法決議,即重寫resolveInstanceMethod/resolveClassMethod方法【消息轉(zhuǎn)發(fā)】如果動(dòng)態(tài)方法決議還是沒有找到,則進(jìn)行消息轉(zhuǎn)發(fā),消息轉(zhuǎn)發(fā)中有兩次補(bǔ)救機(jī)會(huì):快速轉(zhuǎn)發(fā)+慢速轉(zhuǎn)發(fā)如果轉(zhuǎn)發(fā)之后也沒有,則程序直接報(bào)錯(cuò)崩潰
unrecognized selector sent to instance



















