動態(tài)方法解析和轉(zhuǎn)發(fā)

在上面的例子中,如果 foo沒有找到會發(fā)生什么?通常情況下,程序會在運(yùn)行時掛掉并拋出 unrecognized selector sent to … 的異常。但在異常拋出前,Objective-C 的運(yùn)行時會給你三次拯救程序的機(jī)會:
- Method resolution
- Fast forwarding
- Normal forwarding
Method Resolution
首先,Objective-C 運(yùn)行時會調(diào)用 +resolveInstanceMethod:
或者 +resolveClassMethod:,讓你有機(jī)會提供一個函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)并返回 YES, 那運(yùn)行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程。還是以 foo
為例,你可以這么實(shí)現(xiàn):
void fooMethod(id obj, SEL _cmd)
{
NSLog(@"Doing foo");
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(foo:))
{
class_addMethod([self class], aSEL, (IMP)fooMethod, "v@:"); return YES;
}
return [super resolveInstanceMethod];
}
如果 resolve 方法返回 NO ,運(yùn)行時就會移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)。
PS:iOS 4.3 加入很多新的 runtime 方法,主要都是以 imp 為前綴的方法,比如 imp_implementationWithBlock()用 block 快速創(chuàng)建一個 imp 。 上面的例子可以重寫成:
IMP fooIMP = imp_implementationWithBlock(^(id _self) { NSLog(@"Doing foo");
});
class_addMethod([self class], aSEL, fooIMP, "v@:");
Fast forwarding
如果目標(biāo)對象實(shí)現(xiàn)了 -forwardingTargetForSelector: ,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機(jī)會。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(foo:))
{
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
只要這個方法返回的不是 nil 和 self,整個消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個對象。否則,就會繼續(xù) Normal Fowarding 。
Normal forwarding
這一步是 Runtime 最后一次給你挽救的機(jī)會。首先它會發(fā)送 -
methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果 -methodSignatureForSelector:返回 nil ,Runtime 則會發(fā)出 -doesNotRecognizeSelector:消息,程序這時也就掛掉了。
如果返回了一個函數(shù)簽名,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送 -forwardInvocation:消息給目標(biāo)對象。
NSInvocation 實(shí)際上就是對一個消息的描述,包括selector 以及參數(shù)等信息。所以你可以在 -forwardInvocation:
里修改傳進(jìn)來的 NSInvocation 對象,然后發(fā)送 -invokeWithTarget:
消息給它,傳進(jìn)去一個新的目標(biāo):
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL sel = invocation.selector;
if([alternateObject respondsToSelector:sel])
{
[invocation invokeWithTarget:alternateObject];
}
else
{
[self doesNotRecognizeSelector:sel];
}
}
Cocoa 里很多地方都利用到了消息傳遞機(jī)制來對語言進(jìn)行擴(kuò)展,如 Proxies、NSUndoManager 跟 Responder Chain。NSProxy 就是專門用來作為代理轉(zhuǎn)發(fā)消息的;NSUndoManager 截取一個消息之后再發(fā)送;而 Responder Chain 保證一個消息轉(zhuǎn)發(fā)給合適的響應(yīng)者。
總結(jié)
Objective-C 中給一個對象發(fā)送消息會經(jīng)過以下幾個步驟:
在對象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實(shí)現(xiàn)代碼;
如果沒有找到,Runtime 會發(fā)送 +resolveInstanceMethod:或者 +resolveClassMethod:嘗試去 resolve 這個消息;
如果 resolve 方法返回 NO,Runtime 就發(fā)送 -forwardingTargetForSelector:允許你把這個消息轉(zhuǎn)發(fā)給另一個對象;
如果沒有新的目標(biāo)對象返回, Runtime 就會發(fā)送 -methodSignatureForSelector:和 -forwardInvocation:消息。你可以發(fā)送 -invokeWithTarget:消息來手動轉(zhuǎn)發(fā)消息或者發(fā)送 -doesNotRecognizeSelector:拋出異常。