Runtime — 消息轉(zhuǎn)發(fā)

前言

如果在動(dòng)態(tài)解析階段不做任何處理的話,我們調(diào)用一個(gè)未實(shí)現(xiàn)的方法會(huì)crash,下面來分析一下进宝,crash之前系統(tǒng)還做了什么?

一枷恕、探索消息轉(zhuǎn)發(fā)

1. instrumentObjcMessageSends打開log開關(guān)

#import <Foundation/Foundation.h>
#import "DZStudent.h"

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        DZStudent *student = [DZStudent alloc] ;
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);

    }
    return 0;
}

extern void instrumentObjcMessageSends(BOOL flag):是蘋果的私有API党晋,我們可以控制log開關(guān),打印日志信息徐块。

日志文件位置:/tmp/msgSend-xxx

日志文件位置

2. 查看日志文件

我們可以發(fā)現(xiàn)未玻,方法在調(diào)用報(bào)失敗doesNotRecognizeSelector之前的調(diào)用順序:resolveInstanceMethod -> forwardingTargetForSelector -> methodSignatureForSelector -> doesNotRecognizeSelector

resolveInstanceMethod是動(dòng)態(tài)方法決議胡控,我們上一文已經(jīng)做了分析扳剿,本文只針對(duì)后邊的方法進(jìn)行源碼分析。

二铜犬、快速轉(zhuǎn)發(fā)

1. forwardingTargetForSelector分析

我們?nèi)炙阉骱笪柚眨l(fā)現(xiàn)這個(gè)方法是NSObject中實(shí)現(xiàn)的方法轻庆,只做了返回nil的操作:

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

我們可以結(jié)合方法介紹或者官方文檔來進(jìn)行分析:

2. 方法說明(看discussion):

  1. 該方法的目的就是不能處理方法的時(shí)候癣猾,交給另外一個(gè)對(duì)象來執(zhí)行敛劝,但是不能返回self,否則會(huì)一直找不到陷入死循環(huán)纷宇。
  2. 該方法效率很高夸盟,如果不實(shí)現(xiàn)或者返回nil,會(huì)走到相對(duì)效率低的forwardInvocation: 方法進(jìn)行處理像捶。
  3. 所以我們稱forwardingTargetForSelector快速轉(zhuǎn)發(fā)上陕,forwardInvocation慢速轉(zhuǎn)發(fā)
  4. 被轉(zhuǎn)發(fā)的消息接收者拓春,參數(shù)和返回值等需要和原方法相同释簿。

3. 方法使用

當(dāng)訪問DZStudent未實(shí)現(xiàn)的saySomething方法時(shí),可以使用- (id)forwardingTargetForSelector:(SEL)aSelector進(jìn)行方法轉(zhuǎn)發(fā)硼莽,用DZTeacher這個(gè)實(shí)現(xiàn)saySomething方法的對(duì)象來接收庶溶,具體實(shí)現(xiàn)代碼如下:

main.m

DZStudent *student = [DZStudent alloc];
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);

DZStudent.m

// 消息轉(zhuǎn)發(fā)流程
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(saySomething)) {
        return [DZTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

DZTeacher.m

@implementation DZTeacher
- (void)saySomething{
    NSLog(@"%s",__func__);
}
@end

打印結(jié)果

// 失敗打印
2020-06-17 15:58:28.646428+0800 008-方法查找-消息轉(zhuǎn)發(fā)[17172:5671635] -[DZStudent saySomething]: unrecognized selector sent to instance 0x101805980
2020-06-17 15:58:28.658327+0800 008-方法查找-消息轉(zhuǎn)發(fā)[17172:5671635] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DZStudent saySomething]: unrecognized selector sent to instance 0x101805980'

// 成功打印
2020-06-17 14:58:14.506074+0800 008-方法查找-消息轉(zhuǎn)發(fā)[10077:5556271] -[DZStudent forwardingTargetForSelector:] -- saySomething
2020-06-17 14:58:14.507704+0800 008-方法查找-消息轉(zhuǎn)發(fā)[10077:5556271] -[DZTeacher saySomething]

通俗點(diǎn)講,這個(gè)方法的作用就是懂鸵,自己的活自己干不了偏螺,就交給能干活的人去干。

三匆光、慢速轉(zhuǎn)發(fā)

當(dāng)快速轉(zhuǎn)發(fā)流程也沒有實(shí)現(xiàn)套像,或者返回nil,就進(jìn)入慢速轉(zhuǎn)發(fā)流程终息。

1. methodSignatureForSelector

同樣在源碼中全局搜索之后夺巩,我們發(fā)現(xiàn)這個(gè)方法也是NSObject中實(shí)現(xiàn)的方法:

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

官方文檔:

方法說明:

該方法用于協(xié)議的實(shí)現(xiàn),如果有對(duì)象未能直接實(shí)現(xiàn)的消息周崭,則重寫此方法返回適當(dāng)?shù)?strong>方法簽名劲够。然后將簽名對(duì)象作為參數(shù)傳給forwardInvocation方法,在forwardInvocation里邊將消息給能處理該消息的對(duì)象休傍,避免最后調(diào)用didNotRecognizeSelector方法導(dǎo)致崩潰征绎。

下來我們繼續(xù)了解 forwardInvocation 方法:

2. forwardInvocation

同樣是在源碼中全局搜索之后,我們發(fā)現(xiàn)這個(gè)方法也是NSObject中實(shí)現(xiàn)的:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

官方文檔:

forwardInvocationmethodSignatureForSelector必須是同時(shí)重寫磨取。并且該方法可以自由指派多個(gè)對(duì)象接受該消息人柿。

3. doesNotRecognizeSelector

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

我們可以發(fā)現(xiàn),最終是doesNotRecognizeSelector方法拋出的異常忙厌,所以我們可以重寫forwardInvocation方法凫岖,這樣不執(zhí)行父類的方法,程序就不會(huì)崩潰了逢净。

4. 方法使用

forwardInvocation方法中哥放,我們可以把這個(gè)方法看成是一個(gè)未知方法收集箱歼指,在這里可以隨意選擇你可以處理的方法,進(jìn)行歸類集中處理甥雕。

main.m

#import <Foundation/Foundation.h>
#import "DZStudent.h"

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DZStudent *student = [DZStudent alloc];
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);
    }
    return 0;
}

DZTeacher.m

@implementation DZTeacher

- (void)saySomething{
    NSLog(@"%s",__func__);
}
@end

DZStudent.m
備注:關(guān)于方法簽名串"v@:"可以參考官方文檔:方法簽名Type Encodings

// 返回一個(gè)方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);

   SEL aSelector = [anInvocation selector];
   if ([[DZTeacher alloc] respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:[DZTeacher alloc]];
   else
       [super forwardInvocation:anInvocation];
}
// 打印
2020-06-17 17:17:08.148187+0800 008-方法查找-消息轉(zhuǎn)發(fā)[24033:5801203] -[DZStudent methodSignatureForSelector:] -- saySomething
2020-06-17 17:17:08.149424+0800 008-方法查找-消息轉(zhuǎn)發(fā)[24033:5801203] -[DZStudent forwardInvocation:]

當(dāng)然此處轉(zhuǎn)發(fā)方法也可以什么都不做處理踩身,也僅僅是轉(zhuǎn)發(fā)不出去而已,并不會(huì)崩潰社露。

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);
}
// 打印
2020-06-17 18:24:21.692113+0800 008-方法查找-消息轉(zhuǎn)發(fā)[32243:5925309] -[DZStudent methodSignatureForSelector:] -- saySomething
2020-06-17 18:24:21.699464+0800 008-方法查找-消息轉(zhuǎn)發(fā)[32243:5925309] -[DZStudent forwardInvocation:]

四挟阻、總結(jié)

  1. 當(dāng)動(dòng)態(tài)方法決議也沒有做處理時(shí),就會(huì)進(jìn)入快速轉(zhuǎn)發(fā)(forwardingTargetForSelector)階段峭弟。
  2. 如果快速轉(zhuǎn)發(fā)也沒有做處理附鸽,會(huì)繼續(xù)到慢速轉(zhuǎn)發(fā)(forwardInvocation)階段。
  3. 即使forwardInvocation中不實(shí)現(xiàn)后續(xù)方法也不會(huì)崩潰瞒瘸。
  4. forwardInvocationforwardingTargetForSelector類似坷备,都可以將A類的方法轉(zhuǎn)發(fā)的B類的實(shí)現(xiàn)中去,但是forwardInvocation優(yōu)點(diǎn)是更加靈活情臭,forwardingTargetForSelector只能轉(zhuǎn)發(fā)發(fā)到固定的一個(gè)對(duì)象省撑。而forwardInvocation可以轉(zhuǎn)發(fā)的多個(gè)對(duì)象中去,甚至不做處理谎柄,也僅僅是轉(zhuǎn)發(fā)不出去而已丁侄,并不會(huì)崩潰。

消息的流轉(zhuǎn)及對(duì)應(yīng)的作用如下所示:

流轉(zhuǎn):消息發(fā)送 -> 消息查找 -> 動(dòng)態(tài)方法決議 -> 快速轉(zhuǎn)發(fā) -> 慢速轉(zhuǎn)發(fā)
作用:像某個(gè)對(duì)象或類發(fā)送消息 -> 自己有沒有處理 -> 自己有沒有特殊處理(動(dòng)態(tài)方法決議) -> 指定的人有沒有處理 -> 愛誰處理誰處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朝巫,一起剝皮案震驚了整個(gè)濱河市鸿摇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劈猿,老刑警劉巖拙吉,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異揪荣,居然都是意外死亡筷黔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門仗颈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佛舱,“玉大人,你說我怎么就攤上這事挨决∏胱妫” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵脖祈,是天一觀的道長肆捕。 經(jīng)常有香客問我,道長盖高,這世上最難降的妖魔是什么慎陵? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任眼虱,我火速辦了婚禮,結(jié)果婚禮上席纽,老公的妹妹穿的比我還像新娘捏悬。我一直安慰自己,他們只是感情好胆筒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布邮破。 她就那樣靜靜地躺著诈豌,像睡著了一般仆救。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矫渔,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天彤蔽,我揣著相機(jī)與錄音,去河邊找鬼庙洼。 笑死顿痪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的油够。 我是一名探鬼主播蚁袭,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼石咬!你這毒婦竟也來了揩悄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤鬼悠,失蹤者是張志新(化名)和其女友劉穎删性,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焕窝,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹬挺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了它掂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巴帮。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虐秋,靈堂內(nèi)的尸體忽然破棺而出榕茧,到底是詐尸還是另有隱情,我是刑警寧澤熟妓,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布雪猪,位于F島的核電站,受9級(jí)特大地震影響起愈,放射性物質(zhì)發(fā)生泄漏只恨。R本人自食惡果不足惜译仗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望官觅。 院中可真熱鬧纵菌,春花似錦、人聲如沸休涤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽功氨。三九已至序苏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捷凄,已是汗流浹背忱详。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跺涤,地道東北人匈睁。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像桶错,于是被迫代替她去往敵國和親航唆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354