Runtime下的消息發(fā)送/轉(zhuǎn)發(fā)

發(fā)送消息后:(eg.[instanceperformSelector:@selector(selname)])

1.

先一層一層尋找方法

Runtime在GitHub開源了一部分源碼:?objc4

objc-runtime-new.mm文件的 lookUpImpOrForward 方法中可以看到從子到父尋找的過程。?

如果找不到,進入第二步

2.

可以看到源碼,這里調(diào)用了_class_resolveMethod方法

if(resolver? &&? !triedResolver) {

runtimeLock.unlockRead();

_class_resolveMethod(cls, sel, inst);

// Don't cache the result; we don't hold the lock so it may have

// changed already. Re-do the search from scratch instead.

triedResolver =YES;

gotoretry;

}

就是NSObject的這個方法:(BOOL)resolveInstanceMethod:(SEL)sel

在這個方法中可以新加方法,

返回值:YESif the method was found and added to the receiver, otherwiseNO.

但是源碼里并沒有對返回值進行判斷,就是說無論返回yes或者no,都會retry,并且只要addmethod成功就算返回NO也依然會調(diào)用。

還沒找到就進入第三步:

3.

// If neither self nor its superclasses implement

// it, forward the message because self might know

// someone who does.? This is a "chained" forward...

if (! size) return [self forward: sel: args];

源碼如上,這里應(yīng)該是開始調(diào)用下面這個方法:

?(id)forwardingTargetForSelector:(SEL)aSelector

如果一個對象實現(xiàn)了這個方法,并返回一個非nil的結(jié)果,則這個被返回的對象會作為消息的新接收者,且消息會被分發(fā)到這個對象。當(dāng)然這個對象不能是self自身,否則就是出現(xiàn)無限循環(huán)。當(dāng)然,如果我們沒有指定相應(yīng)的對象來處理aSelector,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果。

如果forward返回空,進入第四步:

4.

// Message self with the specified selector and arguments

return objc_msgSendv (self, sel, size, args);

應(yīng)該是在objc_msgSendv方法中開始了下面的過程:

先調(diào)用

-?(NSMethodSignature?*)methodSignatureForSelector:(SEL)aSelector

獲取了NSMethodSignature, 再調(diào)用下面的forwardInvocation,如果沒有實現(xiàn)上述方法,NSObject的默認實現(xiàn)是unrecognized selector。

-?(void)forwardInvocation:(NSInvocation?*)anInvocation

在這個方法中使用anInvocation-invoke 結(jié)束消息;

如果沒有ivnoke 依然unrecognized selector


下面我們詳細討論一下這四個步驟。

動態(tài)方法解析

對象在接收到未知的消息時,首先會調(diào)用所屬類的類方法+resolveInstanceMethod:(實例方法)或 者+resolveClassMethod:(類方法)。在這個方法中,我們有機會為該未知消息新增一個”處理方法”“。不過使用該方法的前提是我們已經(jīng) 實現(xiàn)了該”處理方法”,只需要在運行時通過class_addMethod函數(shù)動態(tài)添加到類里面就可以了。如下代碼所示:

void?functionForMethod1(id?self,?SEL?_cmd)?{

NSLog(@"%@,?%p",?self,?_cmd);

}

+?(BOOL)resolveInstanceMethod:(SEL)sel?{

NSString?*selectorString?=?NSStringFromSelector(sel);

if([selectorString?isEqualToString:@"method1"])?{

class_addMethod(self.class,?@selector(method1),?(IMP)functionForMethod1,"@:");

}

return [super resolveInstanceMethod:sel];

}

備用接收者

如果在上一步無法處理消息,則Runtime會繼續(xù)調(diào)以下方法:

-?(id)forwardingTargetForSelector:(SEL)aSelector

如果一個對象實現(xiàn)了這個方法,并返回一個非nil的結(jié)果,則這個對象會作為消息的新接收者,且消息會被分發(fā)到這個對象。當(dāng)然這個對象不能是self自身,否則就是出現(xiàn)無限循環(huán)。當(dāng)然,如果我們沒有指定相應(yīng)的對象來處理aSelector,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果。

使用這個方法通常是在對象內(nèi)部,可能還有一系列其它對象能處理該消息,我們便可借這些對象來處理消息并返回,這樣在對象外部看來,還是由該對象親自處理了這一消息。如下代碼所示:

@interface?SUTRuntimeMethodHelper?:?NSObject

-?(void)method2;

@end@implementation?SUTRuntimeMethodHelper

-?(void)method2?{

NSLog(@"%@,?%p",?self,?_cmd);

}

@end

#pragma?mark?-

@interface?SUTRuntimeMethod?()?{SUTRuntimeMethodHelper?*_helper;}

@end

@implementation?SUTRuntimeMethod

+?(instancetype)object?{

return [[self?alloc]?init];

}

-?(instancetype)init?{

self?=?[super init];

if(self?!=?nil)?{

_helper?=?[[SUTRuntimeMethodHelper?alloc]?init];

}

return self;

}

-?(void)test?{

[self?performSelector:@selector(method2)];

}

-?(id)forwardingTargetForSelector:(SEL)aSelector?{

NSLog(@"forwardingTargetForSelector");

NSString?*selectorString?=?NSStringFromSelector(aSelector);

//?將消息轉(zhuǎn)發(fā)給_helper來處理

if([selectorString?isEqualToString:@"method2"])?{

return_helper;

}

return[superforwardingTargetForSelector:aSelector];

}

@end

這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象上。但這一步無法對消息進行處理,如操作消息的參數(shù)和返回值。

完整消息轉(zhuǎn)發(fā)

如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了。此時會調(diào)用以下方法:

-?(void)forwardInvocation:(NSInvocation?*)anInvocation

運行時系統(tǒng)會在這一步給消息接收者最后一次機會將消息轉(zhuǎn)發(fā)給其它對象。對象會創(chuàng)建一個表示消息的NSInvocation對象,把與尚未處理的消息 有關(guān)的全部細節(jié)都封裝在anInvocation中,包括selector,目標(biāo)(target)和參數(shù)。我們可以在forwardInvocation 方法中選擇將消息轉(zhuǎn)發(fā)給其它對象。

forwardInvocation:方法的實現(xiàn)有兩個任務(wù):

1. 定位可以響應(yīng)封裝在anInvocation中的消息的對象。這個對象不需要能處理所有未知消息。

2. 使用anInvocation作為參數(shù),將消息發(fā)送到選中的對象。anInvocation將會保留調(diào)用結(jié)果,運行時系統(tǒng)會提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者。

不過,在這個方法中我們可以實現(xiàn)一些更復(fù)雜的功能,我們可以對消息的內(nèi)容進行修改,比如修改一個參數(shù)等,然后再去觸發(fā)消息。另外,若發(fā)現(xiàn)某個消息不應(yīng)由本類處理,則應(yīng)調(diào)用父類的同名方法,以便繼承體系中的每個類都有機會處理此調(diào)用請求。

還有一個很重要的問題,我們必須重寫以下方法:

-?(NSMethodSignature?*)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象。因此我們必須重寫這個方法,為給定的selector提供一個合適的方法簽名。

完整的示例如下所示:

-?(NSMethodSignature?*)methodSignatureForSelector:(SEL)aSelector?{

? ? NSMethodSignature?*signature?=?[supermethodSignatureForSelector:aSelector];?

? ? if(!signature)?{

? ? ? ? if([SUTRuntimeMethodHelper?instancesRespondToSelector:aSelector])?{

? ? ? ? ? ? signature?=?[SUTRuntimeMethodHelper?instanceMethodSignatureForSelector:aSelector];

? ? ? ? }

? }

? ?returnsignature;

}

-?(void)forwardInvocation:(NSInvocation?*)anInvocation?{

? ? if([SUTRuntimeMethodHelper?instancesRespondToSelector:anInvocation.selector])?{

? ? ? ? [anInvocation?invokeWithTarget:_helper];

? ? }

}

NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法,它不會轉(zhuǎn)發(fā)任何消息。這樣,如果不在以上所述的三個步驟中處理未知消息,則會引發(fā)一個異常。

從某種意義上來講,forwardInvocation:就像一個未知消息的分發(fā)中心,將這些未知的消息轉(zhuǎn)發(fā)給其它對象。或者也可以像一個運輸站一樣將所有未知消息都發(fā)送給同一個接收對象。這取決于具體的實現(xiàn)。

消息轉(zhuǎn)發(fā)與多重繼承

回過頭來看第二和第三步,通過這兩個方法我們可以允許一個對象與其它對象建立關(guān)系,以處理某些未知消息,而表面上看仍然是該對象在處理消息。通過這 種關(guān)系,我們可以模擬“多重繼承”的某些特性,讓對象可以“繼承”其它對象的特性來處理一些事情。不過,這兩者間有一個重要的區(qū)別:多重繼承將不同的功能 集成到一個對象中,它會讓對象變得過大,涉及的東西過多;而消息轉(zhuǎn)發(fā)將功能分解到獨立的小的對象中,并通過某種方式將這些對象連接起來,并做相應(yīng)的消息轉(zhuǎn) 發(fā)。

不過消息轉(zhuǎn)發(fā)雖然類似于繼承,但NSObject的一些方法還是能區(qū)分兩者。如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來像是繼承,則可以重寫這些方法,如以下代碼所示:

-?(BOOL)respondsToSelector:(SEL)aSelector???{

if(?[superrespondsToSelector:aSelector]?)

returnYES;

else{

/*?

Here,?test?whether?the?aSelector?message?can be?forwarded?to?another?object?and?whether?that object?can?respond?to?it.?Return?YES?if?it?can.

*/

}

returnNO;

}


相關(guān)文章:

[Cocoa]深入淺出 Cocoa 之消息

Objective-C Runtime

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內(nèi)容