在前面兩章中介紹了方法消息的處理流程,宏觀上來說,方法的本質(zhì)就是對消息的發(fā)送,處理消息的過程呢缰冤,我們經(jīng)歷了objc_msgSend快速查找、慢速查找喳魏。在前面兩個環(huán)節(jié)中棉浸,依然在本類、父類繼承鏈截酷、元類繼承緩存中找未找到消息涮拗,又未采取動態(tài)方法決議乾戏,對未查找到的方法實現(xiàn)resolveInstancMethod
迂苛,則就會報錯奔潰三热。這樣對于開發(fā)者來說是不愿看到的,所以對消息的處理就來到了新的層次三幻,進(jìn)行消息轉(zhuǎn)發(fā)就漾,本章內(nèi)容將圍繞這個展開。
鋪墊
通過前面分析lookUpImpOrForward
念搬,既然是尋找或者轉(zhuǎn)發(fā)抑堡,那在沒尋找到的情況下,它是怎么轉(zhuǎn)發(fā)的呢朗徊?入口又是在哪首妖?
- 通過
instrumentObjcMessageSends
分析方法調(diào)用順序
換一個思路,既然動態(tài)決議后爷恳,如果沒有對Imp進(jìn)行操作有缆,就會崩潰,那可以通過該方法檢測奔潰時方法的調(diào)用情況温亲。lookUpImpOrForward -> log_and_fill_cache -> logMessageSend
棚壁,objcMsgLogEnabled =YES
是進(jìn)入這個流程的關(guān)鍵.
//static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver);
}
//-------------------------------------------------
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
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;
}
// 1: objcMsgLogEnabled 控制開關(guān)
// 2: extern
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;
}
// SUPPORT_MESSAGE_LOGGING
#endif
/tmp/
這個路徑就是就是將奔潰日志輸出到本地臨時緩存了,
在main.m中調(diào)用Book的burnBook的未實現(xiàn)方法,extern
:這是一個關(guān)鍵字栈虚,是告訴編譯器在編譯時不要報錯袖外,在該類中不存在的方法,請去別的類查找魂务。
使得objcMsgLogEnabled =true
曼验,則就可以查看到在本地生成的一個文件。調(diào)用依然會奔潰粘姜,但log會輸出的蚣驼。
其中
forwardingTargetForSelector
就是快速轉(zhuǎn)發(fā)方法;慢速轉(zhuǎn)發(fā)則是methodSignatureForSelector
相艇,其實還搭配forwardInvocation
使用颖杏。
消息轉(zhuǎn)發(fā)-快速轉(zhuǎn)發(fā)
我們該怎么使用forwardingTargetForSelector
呢?這個時候可以瞄一瞄蘋果文檔
如果有無法識別的消息坛芽,就將其轉(zhuǎn)發(fā)到指定的對象留储。那我們就可以進(jìn)行一個操作,再定義一個English的類咙轩。如果識別到burnBook被調(diào)用获讳,我們就將其轉(zhuǎn)發(fā)給另外一個類的方法中,English類中實現(xiàn)了這個方法活喊,就會在English中找尋這個方法丐膝。
// English.m
-(void)burnBook
{
NSLog(@"English burn book");
}
Book中將方法轉(zhuǎn)發(fā)出去,把目標(biāo)對象返回。
#import "Book.h"
#import "English.h"
@implementation Book
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"burnBook"]) {
return [English alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
轉(zhuǎn)發(fā)到English
帅矗,English
實現(xiàn)了該burnBook
方法偎肃,最終會打印出結(jié)果,也不會報錯浑此。
這樣我們就可以在開發(fā)過程中很好的利用這樣一點累颂,結(jié)合動態(tài)方法決議,在需要的地方添加方法凛俱,避免應(yīng)用奔潰紊馏,或者利用運(yùn)行時動態(tài)執(zhí)行一些自定義方法都是很好的方向。
消息轉(zhuǎn)發(fā)-慢速轉(zhuǎn)發(fā)
在快速轉(zhuǎn)發(fā)消息之后蒲犬,就會來到慢速轉(zhuǎn)發(fā)(標(biāo)準(zhǔn)轉(zhuǎn)發(fā))消息朱监。找到運(yùn)行時的方法methodSignatureForSelector
。
返回方法的簽名原叮,在蘋果的文檔中解釋了方法的使用場景赫编,也指出在轉(zhuǎn)發(fā)消息時要創(chuàng)建
NSInvocation
對象。我們先驗證下log里面方法調(diào)用順序篇裁,依然是調(diào)用
burnBook
沛慢,但是不在forwardingTargetForSelector
中處理。再實現(xiàn)方法簽名方法达布,返回父類方法团甲,程序繼續(xù)奔潰,但是也表示我們的方法按照log中的順序走下來了黍聂。現(xiàn)在躺苦,我們對其進(jìn)行簽名,并實現(xiàn)
forwardInvocation
:經(jīng)過慢速轉(zhuǎn)發(fā)产还,程序已經(jīng)不再奔潰匹厘,它已經(jīng)將消息轉(zhuǎn)發(fā)出去,自己也不再處理脐区。
方法簽名圖
image.png
我們可以查看下NSInvocation
結(jié)構(gòu)
我們可以驗證下簽名之后的
anInvocation
中有哪些東西表明簽名后的信息都傳遞到了
- (void)forwardInvocation:(NSInvocation *)anInvocation
我們可以在將其消息轉(zhuǎn)發(fā)給
English
消息轉(zhuǎn)發(fā)流程
我們發(fā)現(xiàn)和我們之前分析的log文件中方法調(diào)用順序不一致愈诚,那么我們可以再做一個操作,實現(xiàn)
resolveInstanceMethod
牛隅,打印結(jié)果炕柔,我們就能發(fā)現(xiàn),與msgSends-10393
中的方法執(zhí)行順序的一致
經(jīng)過一系列的轉(zhuǎn)發(fā)媒佣,我們可以大致總結(jié)到以下一個流程