前言
在Objective-C中,如果只在頭文件中聲明了方法,但沒有在m文件中實(shí)現(xiàn)該方法,如果調(diào)用該方法,通常情況下程序會(huì)崩潰并拋出unrecognized selector sent to instance的異常。
而在異常拋出之前,Runtime會(huì)給你三次拯救程序的機(jī)會(huì):
- Method resolution
- Fast forwarding
- Normal forwarding
下圖是動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)的圖

1. Method resolution
Runtime首先給我們提供的一次機(jī)會(huì),動(dòng)態(tài)方法解析,可以讓我們在運(yùn)行時(shí)完成向特定類添加特定方法實(shí)現(xiàn)的操作。如下例子:
@interface TestClass : NSObject
- (int)print:(int)count;
+ (void)classPrint;
@end
在TestClass類中,我們聲明了一個(gè)類方法classPrint和實(shí)例方法print,但是,并沒有在m文件中實(shí)現(xiàn)他們。此時(shí)編譯器會(huì)出現(xiàn)警告
Method definition for 'print:' not found
Method definition for 'classPrint' not found
如果在代碼中調(diào)用這兩個(gè)方法,編譯不會(huì)出錯(cuò),但運(yùn)行的時(shí)候會(huì)出錯(cuò),也就是拋出unrecognized selector sent to instance的異常。
我們使用動(dòng)態(tài)方法解析來在運(yùn)行時(shí)為TestClass類添加這兩個(gè)方法的實(shí)現(xiàn),其中類方法通過resolveClassMethod來添加,實(shí)例方法通過resolveInstanceMethod來添加。
int missingPrint(id obj, SEL _cmd, int count)
{
NSLog(@"Wow, you found a missing method and count is %d", count);
return count * 100;
}
void missingClassPrint(id obj, SEL _cmd)
{
NSLog(@"調(diào)用了missingClassPrint函數(shù)");
}
@implementation TestClass
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"調(diào)用resolveClassMethod?。?!");
if(sel == @selector(classPrint)) {
class_addMethod(object_getClass(self), sel, (IMP)missingClassPrint, "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"調(diào)用resolveInstanceMethod?。?!");
if(sel == @selector(print:)) {
class_addMethod([self class], sel, (IMP)missingPrint, "I@:I");//其中v@:表示方法的參數(shù)和返回值,詳見Type Encodings
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
上面代碼中,我們定義了兩個(gè)C函數(shù),missingPrint函數(shù)用于綁定實(shí)例方法,missingClassPrint用于綁定類方法。在resolveClassMethod方法和resolveInstanceMethod方法中,通過class_addMethod方法為類動(dòng)態(tài)添加方法實(shí)現(xiàn)。
如此一來,調(diào)用classPrint和print:這兩個(gè)方法時(shí),Runtime會(huì)解析成對(duì)應(yīng)的missingClassPrint方法和missingPrint方法。
int main(int argc, const char * argv[])
{
TestClass *t = [[TestClass alloc] init];
[TestClass classPrint];//這里調(diào)用classPrint類方法
int res = [t print:55];//這里調(diào)用print實(shí)例方法
NSLog(@"res = %d", res);
return 0;
}
PS 1:class_addMethod方法的第三個(gè)參數(shù),"v@:",描述的是方法的返回值和傳參,該符號(hào)的具體內(nèi)容可在Type Encodings查看
PS 2:resolveClassMethod和resolveInstanceMethod方法都是類方法,在類方法中的self,實(shí)際上是Class對(duì)象,而不是實(shí)例對(duì)象,只能調(diào)用類方法,不能調(diào)用實(shí)例方法。
2. Fast forwarding
若在第一步中,類中沒有實(shí)現(xiàn)動(dòng)態(tài)解析方法,或者在方法中返回NO,則會(huì)走到Fast forwarding這一步。
對(duì)于實(shí)例方法,需要實(shí)現(xiàn)forwardingTargetForSelector方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(print:)) {
return [MessageForwarding new];//返回另外一個(gè)對(duì)象
//return self //返回self會(huì)導(dǎo)致到第三步Normal forwarding
}
return nil;
}
這里可以看到,只要捕捉到方法選擇器是print方法,則返回一個(gè)新建的MessageForwarding對(duì)象,將該消息重定向到MessageForwarding對(duì)象來處理。這里MessageForwarding類只需聲明并實(shí)現(xiàn)了print方法即可。
若想轉(zhuǎn)發(fā)類方法,則需要重寫forwardingTargetForSelector的類方法
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(classPrint)) {
return [MessageForwarding class];//這里的返回和實(shí)例方法中不一樣
}
return nil;
}
這里返回的是[MessageForwarding class],因?yàn)檎{(diào)用類方法需要的是一個(gè)類對(duì)象,而不是實(shí)例對(duì)象。
3.Normal forwarding
當(dāng)前兩步都沒有實(shí)現(xiàn),或者在Fast forwarding這一步的forwardingTargetForSelector方法中返回self,都會(huì)進(jìn)入Normal forwarding這一步。
首先會(huì)發(fā)送-methodSignatureForSelector: 消息獲得函數(shù)的參數(shù)和返回類型。接著Runtime會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:消息給目標(biāo)對(duì)象。
NSInvocation 實(shí)際上就是對(duì)一個(gè)消息的描述,包括selector 以及參數(shù)等信息。
這里需要我們自己實(shí)現(xiàn)這兩個(gè)方法。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if(!methodSignature){
methodSignature = [NSMethodSignature signatureWithObjCTypes:"I@:I"];
}
return methodSignature;
}
//NSInvocation由NSMethodSignature生成
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"calling forwardInvocation: method");
MessageForwarding *mf = [MessageForwarding new];
if([mf respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:mf];
}
}
這里仍然是將消息轉(zhuǎn)發(fā)給MessageForwarding對(duì)象來處理。
總結(jié)
Objective-C 中給一個(gè)對(duì)象發(fā)送消息會(huì)經(jīng)過以下幾個(gè)步驟:
- 在對(duì)象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實(shí)現(xiàn)代碼;
- 如果沒有找到,Runtime 會(huì)發(fā)送
+resolveInstanceMethod:或者+resolveClassMethod:嘗試去 resolve 這個(gè)消息; - 如果 resolve 方法返回 NO,Runtime 就發(fā)送
-forwardingTargetForSelector:允許你把這個(gè)消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象; - 如果沒有新的目標(biāo)對(duì)象返回, Runtime 就會(huì)發(fā)送
-methodSignatureForSelector:和-forwardInvocation:消息。你可以發(fā)送-invokeWithTarget:消息來手動(dòng)轉(zhuǎn)發(fā)消息或者發(fā)送-doesNotRecognizeSelector:拋出異常。