動態(tài)方法解析和轉(zhuǎn)發(fā)
在上面的例子中穴吹,如果 foo沒有找到會發(fā)生什么握联?通常情況下笑陈,程序會在運行時掛掉并拋出 unrecognized selector sent to … 的異常。但在異常拋出前工秩,Objective-C 的運行時會給你三次拯救程序的機會:
- Method resolution
- Fast forwarding
- Normal forwarding
Method Resolution
首先,Objective-C 運行時會調(diào)用 +resolveInstanceMethod:
或者 +resolveClassMethod:进统,讓你有機會提供一個函數(shù)實現(xiàn)助币。如果你添加了函數(shù)并返回 YES, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程螟碎。還是以 foo
為例眉菱,你可以這么實現(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 ,運行時就會移到下一步:消息轉(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)對象實現(xiàn)了 -forwardingTargetForSelector: 华坦,Runtime 這時就會調(diào)用這個方法愿吹,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會。
- (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 最后一次給你挽救的機會。首先它會發(fā)送 -
methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型条舔。如果 -methodSignatureForSelector:返回 nil 枫耳,Runtime 則會發(fā)出 -doesNotRecognizeSelector:消息,程序這時也就掛掉了逞刷。
如果返回了一個函數(shù)簽名嘉涌,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送 -forwardInvocation:消息給目標(biāo)對象妻熊。
NSInvocation 實際上就是對一個消息的描述夸浅,包括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ìn)行擴展,如 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í)行實現(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:拋出異常。