前言
前面我們分析了消息的查找流程
,快速查找流程
,慢速查找流程
爱榔。如果這兩種方式都沒找到方法的實現(xiàn)掉冶,蘋果給了兩個建議
-
動態(tài)方法決議
:慢速查找流程未找到后真竖,會執(zhí)行一次動態(tài)方法決議 -
消息轉(zhuǎn)發(fā)
:如果動態(tài)方法決議仍然沒有找到實現(xiàn),則進行消息轉(zhuǎn)發(fā)
如果這兩個建議都沒有找到方法的實現(xiàn)厌小,就會崩潰報錯unrecognized selector sent to instance 0x600000c880d0
定義LGPerson
類恢共,其中sayHello實例方法
沒有實現(xiàn),main.m文件
調(diào)用璧亚,崩潰如下
<!-- LGPerson.h文件 -->
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)sayHello;
- (void)sayMaster;
@end
NS_ASSUME_NONNULL_END
<!-- LGPerson.m文件 -->
#import "LGPerson.h"
@implementation LGPerson
- (void)sayMaster {
NSLog(@"%@ : %s",self,__func__);
}
@end
方法找不到的報錯底層
根據(jù)慢速查找
源碼讨韭,我們發(fā)現(xiàn)其報錯后都是走到__objc_msgForward_impcache
方法,以下是報錯流程的源碼
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
匯編實現(xiàn)中查找__objc_forward_handler
沒有找到,在源碼中去掉一個下劃線進行全局搜索_objc_forward_handler
有如下實現(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;
這就是我們?nèi)粘i_發(fā)中最常見的錯誤:沒有實現(xiàn)函數(shù)狰闪,運行程序,崩潰報錯提示
濒生。探索流程總結(jié)
- 由控制臺打印信息
unrecognized selector sent to instance
為出發(fā)點埋泵,向下深挖方法的響應(yīng)流程
- 通過對
LookUpImpOrForward
的分析得出在慢速查找未找到的時候,給imp默認賦值forward_imp
即objc_msgForward_impcache
- 通過對
objc_msgForward_impcache
的查找罪治,發(fā)現(xiàn)在匯編中它只是一個中間函數(shù)丽声,真正指向的是objc_msgForward
- 對
objc_msgForward
的分析得出,方法內(nèi)調(diào)用了objc_forward_handle
獲取返回值存到x17寄存器
觉义,再由TailCallFunctionPointer
方法跳轉(zhuǎn)到x17寄存器內(nèi)真正指向的imp地址 -
_objc_forward_handler
會給定默認的實現(xiàn)objc_defaultForwardHandle
雁社,當imp最終的查找流程全部走完的時候還未找到imp,那么此時就會進入objc_defaultForwardHandle函數(shù)谁撼,將錯誤信息打印出來歧胁。
為了防止方法未實現(xiàn)的崩潰,下面我們從對象方法動態(tài)決議
開始探索
對象方法動態(tài)決議
在慢速查找流程
未找到方法實現(xiàn)時厉碟,循環(huán)查找結(jié)束喊巍,接下來進入動態(tài)方法決議
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
//當消息的快速查找、慢速查找流程都走完還找不到的情況下
//imp會給一個默認值forward_imp箍鼓,進入消息轉(zhuǎn)發(fā)流程
imp = forward_imp;
break;
}
//找到了sel對應(yīng)的imp
if (fastpath(imp)) {
goto done;
}
}
/**
* 如果遍歷查找的過程找到了崭参,會跳過此步驟,取到done分支款咖,進行后續(xù)操作
* 如果找不到何暮,會進行下面這個算法,最終進入resolveMethod_locked函數(shù)
* 此算法真正達到的目的為單例铐殃,保證一個lookUpImpOrForward
* 只執(zhí)行一次resolveMethod_locked
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
return imp;
}
位運算
-
&
:按位與海洼,是雙目運算符。其功能是參與運算的兩數(shù)各對應(yīng)的二進位相與富腊。相同位置數(shù)字都為1時坏逢,結(jié)果位才為1。 -
^
:按位異或赘被,比較的是二進制位是整,相同位置數(shù)字不同為1,相同為0 -
^=
:按位異或民假,比較的是二進制位浮入,相同位置數(shù)字不同為1,相同為0羊异,將結(jié)果賦值為運算符左邊事秀。
單例解讀
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
- 前提條件(初始值)
behavior = 3 LOOKUP_RESOLVER = 2
- 初次判斷操作
behavior & LOOKUP_RESOLVER
= 3 & 2 = 0x11 & 0x10 = 0x10 = 2 - 重置behaivor
behavior
=behavior ^ LOOKUP_RESOLVER
= 3 ^ 2 = 0x11 ^ 0x10 = 0x01 = 1 - 再次進入判斷操作(第二次至無限次)
behavior & LOOKUP_RESOLVER
= 1 & 2 = 0x01 & 0x10 = 0x00 = 0
得出結(jié)論
保證了每一個lookUpImpOrForward
函數(shù)最多只能執(zhí)行一次resolveMethod_locked
(動態(tài)方法決議)彤断,直到behavior被重新賦值
動態(tài)方法決議源碼
調(diào)用一個方法流程,先進入消息快速查詢流程
-> 然后消息慢速查找流程
秽晚,當?shù)讓釉创a已經(jīng)查找了兩遍依然找不到方法的實現(xiàn)瓦糟,此時imp=nil
,理論上來講程序應(yīng)該崩潰赴蝇。但在開發(fā)者的角度菩浙,崩潰表示這個框架不穩(wěn)定,系統(tǒng)很不友善句伶。所以此框架決定再給你一次機會劲蜻,為你提供一個返回自定義imp的機會,resolveMethod_locked
函數(shù)就是動態(tài)消息轉(zhuǎn)發(fā)的入口考余。
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);
//為什么要有這行代碼疫蔓? -- 類方法在元類中是對象方法,所以還是需要查詢元類中對象方法的動態(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
//如果方法解析中將其實現(xiàn)指向其他方法衅胀,則繼續(xù)走方法查找流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
該函數(shù)內(nèi)的三個關(guān)鍵函數(shù)
-
resolveInstanceMethod
:實例方法動態(tài)添加imp -
resolveClassMethod
:類方法動態(tài)添加imp -
lookUpImpOrForwardTryCache
,當完成添加之后酥筝,回到之前的慢速查找流程再來一遍
類動態(tài)方法決議會進入resolveClassMethod
滚躯,然后根據(jù)判斷有可能會再次進入resolveInstanceMethod
為什么?
正常的類對象動態(tài)方法決議會進入resolveClassMethod
嘿歌,這點是毋庸置疑的掸掏,但是類方法查找過程是在元類中查找,那么通過isa的指向圖中可以得知宙帝,類方法的查找過程也是有繼承關(guān)系的丧凤,會一直向上找,找到superMetaClass
步脓,找到rootMetaClass
息裸,最終找到NSObject
,到這一層所有的方法對于NSObject來講都是實例方法所以會調(diào)用resolveInstanceMethod
沪编。
實例方法源碼
針對實例方法調(diào)用
,在快速-慢速查找
均沒有找到實例方法的實現(xiàn)時年扩,我們有一次挽救的機會即嘗試一次動態(tài)方法決議
蚁廓,由于是實例方法,所以會走到resolveInstanceMethod
方法
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// look的是 resolveInstanceMethod --相當于是發(fā)送消息前的容錯處理
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));
}
}
}
主要步驟如下
- 在發(fā)送
resolveInstanceMethod
消息前厨幻,需要查找cls類中是否有該方法的實現(xiàn)相嵌,即通過lookUpImpOrNil
方法又會進入lookUpImpOrForward
慢速查找流程查找resolveInstanceMethod
方法
如果沒有直接返回
如果有則發(fā)送resolveInstanceMethod
消息 - 再次慢速查找實例方法的實現(xiàn)腿时,即通過
lookUpImpOrNil
方法又會進入lookUpImpOrForward
慢速查找流程查找實例方法
崩潰修改
針對實例方法sayHello
未實現(xiàn)的報錯,可以通過在類中重寫resolveInstanceMethod
類方法饭宾,并將其指向其他方法的實現(xiàn)批糟。即在LGPerson中重寫resolveInstanceMethod
類方法,將實例方法sayHello
的實現(xiàn)指向sayMaster
方法實現(xiàn)看铆,如下所示
// LGPerson.m 文件添加如下resolveInstanceMethod方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayHello)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
//獲取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
//獲取sayMaster的實例方法
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
//獲取sayMaster的豐富簽名
const char *type = method_getTypeEncoding(sayMethod);
//將sel的實現(xiàn)指向sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
// 運行工程其打印如下
2021-08-01 17:51:30.226277+0800 DDDDD[5106:762939] sayHello 來了
2021-08-01 17:51:30.226734+0800 DDDDD[5106:762939] <LGPerson: 0x281ecc040> : -[LGPerson sayMaster]
類方法的動態(tài)決議
類方法動態(tài)決議源碼
/*********************************************************
* 解析類方法
* 調(diào)用+resolveClass 方法徽鼎,尋找要添加到類cls 的方法。
* cls 應(yīng)該是一個元類弹惦。
* 不檢查該方法是否已經(jīng)存在否淤。
*********************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//當你為實現(xiàn)resolveClassMethod的時候,此處也不會進入return
//因為系統(tǒng)給resolveClassMethod函數(shù)默認返回NO棠隐,即默認實現(xiàn)了
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//nonmeta容錯處理
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//系統(tǒng)會在此處為你發(fā)送一個消息resolveClassMethod
//當你的這個類檢測了這個消息石抡,并且做了處理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
//那么此時系統(tǒng)會重新查找,此函數(shù)最終會觸發(fā)LookUpImpOrForward
IMP imp = lookUpImpOrNilTryCache(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 resolveClassMethod:%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));
}
}
}
LGPerson
類中添加類方法如下助泽,其中sayHappy
方法沒有實現(xiàn)
<!-- LGPerson.h文件 -->
+ (void)sayNB;
+ (void)sayHappy;
<!-- LGPerson.m文件 -->
+ (void)sayNB {
NSLog(@"%@ : %s",self,__func__);
}
// 添加類方法動態(tài)決議
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayHappy)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(sayNB));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(sayNB));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
// 運行工程其打印如下
2021-08-01 18:46:27.239762+0800 DDDDD[5295:774648] sayHappy 來了
2021-08-01 18:46:27.239814+0800 DDDDD[5295:774648] LGPerson : +[LGPerson sayNB]
注意: resolveClassMethod
類方法的重寫傳入的cls
不再是類啰扛,而是元類∥撕兀可以通過objc_getMetaClass
方法獲取類的元類隐解,原因是類方法在元類中是實例方法
得出結(jié)論
- 通過手動添加
resolveInstanceMethod/resolveClassMethod
可以防止方法未實現(xiàn)崩潰 - 通過判斷當前查找的sel動態(tài)添加一個新的imp
- 此時
sel(sayHello/sayHappy)
對應(yīng)實現(xiàn)的imp不再是sayHello/sayHappy
,而是變成了我動態(tài)指定的sayMaster/sayNB
優(yōu)化
日常開發(fā)中不可能每個類中都實現(xiàn)一份resolveInstanceMethod/resolveClassMethod
暑刃,有沒有更好的解決方案呢厢漩?通過方法慢速查找流程可以發(fā)現(xiàn)其查找路徑有兩條
- 實例方法:
類 -- 父類 -- 根類 -- nil
- 類方法:
元類 -- 根元類 -- 根類 -- nil
它們的共同點是如果前面沒找到,都會來到根類即NSObject
中查找岩臣,所以我們可以將上述的兩個方法整合在一起溜嗜。通過NSObject添加分類的方式來統(tǒng)一處理,而且由于類方法的查找在其繼承鏈中查找的也是實例方法架谎,所以可以將實例方法和類方法統(tǒng)一放在resolveInstanceMethod
方法中處理炸宵,如下所示
@implementation NSObject (LG)
//為實例對象創(chuàng)建一個IMP
- (void) sayMaster {
NSLog(@"%@ : %s",self,__func__);
}
//為元類對象創(chuàng)建創(chuàng)建一個IMP
+ (void) sayNB {
NSLog(@"%@ - %s",self,__func__);
}
#pragma clang diagnostic push
// 讓編譯器忽略錯誤
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sayHello)) {
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(sayHappy)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(sayNB));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(sayNB));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
#pragma clang diagnostic pop
@end
// 運行工程其打印如下
2021-08-01 19:20:31.446141+0800 DDDDD[5405:783621] sayHello 來了
2021-08-01 19:20:31.446775+0800 DDDDD[5405:783621] <LGPerson: 0x2838f8080> : -[NSObject(LG) sayMaster]
2021-08-01 19:20:31.446847+0800 DDDDD[5405:783621] sayHappy 來了
2021-08-01 19:20:31.446881+0800 DDDDD[5405:783621] LGPerson - +[NSObject(LG) sayNB]
這種方式的實現(xiàn),正好與源碼中針對類方法的處理邏輯是一致的谷扣,即完美闡述為什么調(diào)用了類方法動態(tài)方法決議
土全,還要調(diào)用對象方法動態(tài)方法決議
,其根本原因還是類方法在元類中的實例方法
会涎。
當然裹匙,上面這種寫法還是會有其他的問題,比如系統(tǒng)方法也會被更改
末秃,針對這一點是可以優(yōu)化的概页,即我們可以針對自定義類中方法統(tǒng)一方法名的前綴,根據(jù)前綴來判斷是否是自定義方法练慕,然后統(tǒng)一處理自定義方法惰匙,例如可以在崩潰前pop到首頁技掏,主要是用于app線上防崩潰
的處理,提升用戶的體驗项鬼。
aop和oop總結(jié)
oop
:面向?qū)ο缶幊?/code>哑梳,不同類做不同事情,分工明確绘盟。
-
優(yōu)點
:耦合度很低 -
缺點
:代碼冗余鸠真,常規(guī)解決辦法是提取,這樣會有一個公共類奥此。所有人對公共類進行集成弧哎,形成強依賴,也就代表著出現(xiàn)了強耦合
aop
:面向切面編程稚虎,是oop的延伸
-
優(yōu)點
:對業(yè)務(wù)無侵入撤嫩,通過動態(tài)方式將某些方法進行注入 -
缺點
:做了一些判斷,執(zhí)行了很多無關(guān)代碼蠢终,包括系統(tǒng)方法序攘,造成性能消耗,會打斷apple的方法動態(tài)轉(zhuǎn)發(fā)流程寻拂。
消息轉(zhuǎn)發(fā)流程引入
在慢速查找的流程中程奠,我們了解到如果快速+慢速
沒有找到方法實現(xiàn),動態(tài)方法決議
也不行祭钉,就使用消息轉(zhuǎn)發(fā)
瞄沙。但是我們找遍了源碼也沒有發(fā)現(xiàn)消息轉(zhuǎn)發(fā)的相關(guān)源碼,可以通過以下方式來了解慌核,方法調(diào)用崩潰前都走了哪些方法
- 通過
instrumentObjcMessageSends
方式打印發(fā)送消息的日志
objc4-818.2源碼
全局搜索instrumentObjcMessageSends
實現(xiàn)如下
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
探索instrumentObjcMessageSends
流程
- 通過
lookUpImpOrForward --> log_and_fill_cache --> logMessageSend
距境,在logMessageSend
源碼下方找到instrumentObjcMessageSends
的源碼實現(xiàn) - 在
main
中調(diào)用instrumentObjcMessageSends
打印方法調(diào)用的日志信息,有以下兩點準備
- 打開
objcMsgLogEnabled
開關(guān)垮卓,即調(diào)用instrumentObjcMessageSends
方法時傳入YES - 在
main
中通過extern
聲明instrumentObjcMessageSends
方法
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
//使用區(qū)域垫桂,將想要查找的方法用instrumentObjcMessageSends圈起來
//開始監(jiān)聽傳遞參數(shù)為YES,結(jié)束監(jiān)聽傳遞參數(shù)為NO
instrumentObjcMessageSends(YES);
[person sayHello];
// 這里再次賦值為NO粟按,表示只查看sayHello方法
instrumentObjcMessageSends(NO);
}
return 0;
}
- 通過
logMessageSend
源碼了解到消息發(fā)送打印信息存儲在/tmp/msgSends
目錄
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
- 運行代碼并前往
/tmp/msgSends
目錄诬滩,發(fā)現(xiàn)有msgSends
開頭的日志文件,打開發(fā)現(xiàn)在崩潰前執(zhí)行了以下方法
兩次動態(tài)方法決議
:resolveInstanceMethod方法
兩次消息快速轉(zhuǎn)發(fā)
:forwardingTargetForSelector方法
兩次消息慢速轉(zhuǎn)發(fā)
:methodSignatureForSelector + resolveInvocation