OC-底層原理09—消息轉(zhuǎn)發(fā)流程

iOS--OC底層原理文章匯總

在前面兩章中介紹了方法消息的處理流程,宏觀上來說,方法的本質(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

使得objcMsgLogEnabled =true曼验,則就可以查看到在本地生成的一個文件。調(diào)用依然會奔潰粘姜,但log會輸出的蚣驼。

msgSends文件路徑

文件內(nèi)容-調(diào)用方法順序

其中forwardingTargetForSelector就是快速轉(zhuǎn)發(fā)方法;慢速轉(zhuǎn)發(fā)則是methodSignatureForSelector相艇,其實還搭配forwardInvocation使用颖杏。

消息轉(zhuǎn)發(fā)-快速轉(zhuǎn)發(fā)

我們該怎么使用forwardingTargetForSelector 呢?這個時候可以瞄一瞄蘋果文檔

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é)果,也不會報錯浑此。

轉(zhuǎn)發(fā)消息到其他對象

這樣我們就可以在開發(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中的順序走下來了黍聂。
快速轉(zhuǎn)發(fā)->方法簽名

現(xiàn)在躺苦,我們對其進(jìn)行簽名,并實現(xiàn)forwardInvocation
結(jié)果-不崩潰

經(jīng)過慢速轉(zhuǎn)發(fā)产还,程序已經(jīng)不再奔潰匹厘,它已經(jīng)將消息轉(zhuǎn)發(fā)出去,自己也不再處理脐区。

方法簽名圖


image.png

我們可以查看下NSInvocation結(jié)構(gòu)

NSInvocation定義

我們可以驗證下簽名之后的anInvocation中有哪些東西
簽名后的anInvocation

表明簽名后的信息都傳遞到了- (void)forwardInvocation:(NSInvocation *)anInvocation
我們可以在將其消息轉(zhuǎn)發(fā)給English
慢速消息轉(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é)到以下一個流程


消息轉(zhuǎn)發(fā)流程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匕累,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子默伍,更是在濱河造成了極大的恐慌欢嘿,老刑警劉巖衰琐,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異炼蹦,居然都是意外死亡羡宙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門框弛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辛辨,“玉大人捕捂,你說我怎么就攤上這事瑟枫。” “怎么了指攒?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵慷妙,是天一觀的道長。 經(jīng)常有香客問我允悦,道長膝擂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任隙弛,我火速辦了婚禮架馋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘全闷。我一直安慰自己叉寂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布总珠。 她就那樣靜靜地躺著屏鳍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪局服。 梳的紋絲不亂的頭發(fā)上钓瞭,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音淫奔,去河邊找鬼山涡。 笑死,一個胖子當(dāng)著我的面吹牛唆迁,可吹牛的內(nèi)容都是我干的鸭丛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼媒惕,長吁一口氣:“原來是場噩夢啊……” “哼系吩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妒蔚,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤穿挨,失蹤者是張志新(化名)和其女友劉穎月弛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體科盛,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡帽衙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贞绵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厉萝。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖榨崩,靈堂內(nèi)的尸體忽然破棺而出谴垫,到底是詐尸還是另有隱情,我是刑警寧澤母蛛,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布翩剪,位于F島的核電站,受9級特大地震影響彩郊,放射性物質(zhì)發(fā)生泄漏前弯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一秫逝、第九天 我趴在偏房一處隱蔽的房頂上張望恕出。 院中可真熱鬧,春花似錦违帆、人聲如沸浙巫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狈醉。三九已至,卻和暖如春惠险,著一層夾襖步出監(jiān)牢的瞬間苗傅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工班巩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留渣慕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓抱慌,卻偏偏與公主長得像逊桦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抑进,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354