當消息發(fā)送給一個對象時,objc_msgSend通過對象的isa指針獲取到類的結構體,然后在方法分發(fā)表里面查找方法的selector。如果沒有找到selector,則通過objc_msgSend結構體中的指向父類的指針找到其父類满败,并在父類的分發(fā)表里面查找方法的selector。依此叹括,會一直沿著類的繼承體系到達NSObject類算墨。一旦定位到selector,函數(shù)會就獲取到了實現(xiàn)的入口點领猾,并傳入相應的參數(shù)來執(zhí)行方法的具體實現(xiàn)米同。如果最后沒有定位到selector骇扇,則會走消息轉(zhuǎn)發(fā)流程
消息轉(zhuǎn)發(fā)
當一個對象能接收一個消息時,就會走正常的方法調(diào)用流程面粮。但如果一個對象無法接收指定消息時少孝,又會發(fā)生什么事呢?默認情況下熬苍,如果是以[object message]的方式調(diào)用方法稍走,如果object無法響應message消息時,編譯器會報錯柴底。但如果是以perform...的形式來調(diào)用婿脸,則需要等到運行時才能確定object是否能接收message消息。如果不能柄驻,則程序崩潰狐树。
通常,當我們不能確定一個對象是否能接收某個消息時鸿脓,會先調(diào)用respondsToSelector:來判斷一下抑钟。如下代碼所示:
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
當一個對象無法接收某一消息時,就會啟動所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機制野哭,通過這一機制在塔,我們可以告訴對象如何處理未知的消息。默認情況下拨黔,對象接收到未知的消息蛔溃,會導致程序崩潰,通過控制臺篱蝇,我們可以看到以下異常信息:
-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940'
這段異常信息實際上是由NSObject的”doesNotRecognizeSelector“方法拋出的贺待。不過,我們可以采取一些措施态兴,讓我們的程序執(zhí)行特定的邏輯狠持,而避免程序的崩潰疟位。
- 消息轉(zhuǎn)發(fā)機制基本上分為三個步驟:
1.動態(tài)方法解析
2.備用接收者
3.完整轉(zhuǎn)發(fā)
下面我們詳細討論一下這三個步驟瞻润。
動態(tài)方法解析
對象在接收到未知的消息時,首先會調(diào)用所屬類的類方法+resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)甜刻。在這個方法中绍撞,我們有機會為該未知消息新增一個”處理方法””。不過使用該方法的前提是我們已經(jīng)實現(xiàn)了該”處理方法”,只需要在運行時通過class_addMethod函數(shù)動態(tài)添加到類里面就可以了,也就是變相的'將已經(jīng)實現(xiàn)的處理方法的實現(xiàn)地址綁定給”未知消息”';
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(notFunc)) {
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(func)), "v@:");
}
return [super resolveInstanceMethod:sel];
}
備用接收者
如果在上一步無法處理消息得院,則Runtime會繼續(xù)調(diào)以下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果一個對象實現(xiàn)了這個方法傻铣,并返回一個非nil的結果,則這個對象會作為消息的新接收者祥绞,且消息會被分發(fā)到這個對象非洲。當然這個對象不能是self自身鸭限,否則就是出現(xiàn)無限循環(huán)。當然两踏,如果我們沒有指定相應的對象來處理aSelector败京,則應該調(diào)用父類的實現(xiàn)來返回結果。
使用這個方法通常是在對象內(nèi)部梦染,可能還有一系列其它對象能處理該消息赡麦,我們便可借這些對象來處理消息并返回,這樣在對象外部看來帕识,還是由該對象親自處理了這一消息泛粹。如下代碼所示:
//消息重定向(即發(fā)送消息給其他的有這個方法的類)
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(notFunc)) {
if (class_respondsToSelector([Person class], aSelector)) {
return [Person new];
}
}
return [super forwardingTargetForSelector:aSelector];
}
這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象上。但這一步無法對消息進行處理肮疗,如操作消息的參數(shù)和返回值晶姊。
完整消息轉(zhuǎn)發(fā)
如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了伪货。此時會調(diào)用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
運行時系統(tǒng)會在這一步給消息接收者最后一次機會將消息轉(zhuǎn)發(fā)給其它對象帽借。對象會創(chuàng)建一個表示消息的NSInvocation對象,把與尚未處理的消息有關的全部細節(jié)都封裝在anInvocation中超歌,包括selector砍艾,目標(target)和參數(shù)。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象巍举。
forwardInvocation:方法的實現(xiàn)有兩個任務:
- 定位可以響應封裝在anInvocation中的消息的對象脆荷。這個對象不需要能處理所有未知消息。
- 使用anInvocation作為參數(shù)懊悯,將消息發(fā)送到選中的對象蜓谋。anInvocation將會保留調(diào)用結果,運行時系統(tǒng)會提取這一結果并將其發(fā)送到消息的原始發(fā)送者炭分。
不過桃焕,在這個方法中我們可以實現(xiàn)一些更復雜的功能,我們可以對消息的內(nèi)容進行修改捧毛,比如追回一個參數(shù)等观堂,然后再去觸發(fā)消息。另外呀忧,若發(fā)現(xiàn)某個消息不應由本類處理师痕,則應調(diào)用父類的同名方法,以便繼承體系中的每個類都有機會處理此調(diào)用請求而账。
還有一個很重要的問題胰坟,我們必須重寫以下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象。因此我們必須重寫這個方法泞辐,為給定的selector提供一個合適的方法簽名笔横。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *s = [super methodSignatureForSelector:aSelector];
if (!s) {
s = [[Person new] methodSignatureForSelector:aSelector];
}
return s;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (class_respondsToSelector([Person class], anInvocation.selector)) {
[anInvocation invokeWithTarget:[Person new]];
}else {
[super forwardInvocation: anInvocation];
}
}
NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法竞滓,它不會轉(zhuǎn)發(fā)任何消息。這樣吹缔,如果不在以上所述的三個步驟中處理未知消息虽界,則會引發(fā)一個異常。
從某種意義上來講涛菠,forwardInvocation:就像一個未知消息的分發(fā)中心莉御,將這些未知的消息轉(zhuǎn)發(fā)給其它對象∷锥常或者也可以像一個運輸站一樣將所有未知消息都發(fā)送給同一個接收對象礁叔。這取決于具體的實現(xiàn)。