<code>[receiver message]</code>调用方法时,如果在message方法在receiver对象的类继承体系中没有找到方法,那怎么办?一般情况下,程序在运行时就会Crash掉,抛出 unrecognized selector sent to …类似这样的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。
<ul>
<li>Method Resolution
用所属类的类方法+(BOOL)resolveInstanceMethod:(实例方法)或者+(BOOL)resolveClassMethod:(类方法),在此方法里添加class_addMethod函数。一般用于@dynamic动态属性。(当一个属性声明为@dynamic,就是向编译器保证编译时不用管/get实现,一定会在运行时实现)。
</li>
<li>Fast Forwarding
如果上一步无法响应消息,调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,将消息接受者转发到另一个对象target(不能为self,否则死循环)。
</li>
<li>Normal Forwarding
如果上一步无法响应消息:
调用方法签名- (NSMethodSignature )methodSignatureForSelector:(SEL)aSelector,方法签名目的将函数的参数类型和返回值封装;
如果返回非nil,则创建一个NSInvocation对象利用方法签名和selector封装未被处理的消息,作为参数传递给- (void)forwardInvocation:(NSInvocation )anInvocation。
这一步比较耗时。
</li>
</ul>
如果以上步骤(消息传递和消息转发)还是不能响应消息,则调动doesNotRecognizeSelector:方法,抛出异常。
unrecognized selector sent to instance
(消息转发可以利用转移消息接受对象,实现伪多重继承的效果。)
Method Resolution
首先Objective-C在运行时调用<code>+ resolveInstanceMethod:</code>或<code>+ resolveClassMethod:</code>方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。
举一个简单例子,定义一个类Message,它主要定义一个方法sendMessage,下面就是它的设计与实现:
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
- (void)sendMessage:(NSString *)word
{
NSLog(@"normal way : send message = %@", word);
}
@end
如果我在viewDidLoad方法中创建Message对象并调用<code>sendMessage</code>方法:
- (void)viewDidLoad {
[super viewDidLoad];
Message *message = [Message new];
[message sendMessage:@"Sam Lau"];
}
控制台会打印以下信息:
normal way : send message = Sam Lau
但现在我将原来<code>sendMessage</code>方法实现给注释掉,覆盖<code>resolveInstanceMethod</code>方法:
#pragma mark - Method Resolution
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sendMessage:)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {
NSLog(@"method resolution way : send message = %@", word);
}), "v@*");
}
return YES;
}
控制台就会打印以下信息:
method resolution way : send message = Sam Lau
注意到上面代码有这样一个字符串"v@*,它表示方法的参数和返回值,详情请参考Type Encodings
如果resolveInstanceMethod方法返回NO,运行时就跳转到下一步:消息转发(Message Forwarding)
Fast Forwarding
如果目标对象实现<code>- forwardingTargetForSelector:</code>方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding。
继续上面Message类的例子,将<code>sendMessage</code>和<code>resolveInstanceMethod</code>方法注释掉,然后添加<code>forwardingTargetForSelector</code>方法的实现:
#pragma mark - Fast Forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sendMessage:)) {
return [MessageForwarding new];
}
return nil;
}
此时还缺一个转发消息的类<code>MessageForwarding</code>,这个类的设计与实现如下:
@interface MessageForwarding : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation MessageForwarding
- (void)sendMessage:(NSString *)word
{
NSLog(@"fast forwarding way : send message = %@", word);
}
@end
此时,控制台会打印以下信息:
fast forwarding way : send message = Sam Lau
这里叫Fast,是因为这一步不会创建NSInvocation对象,但Normal Forwarding会创建它,所以相对于更快点。
Normal Forwarding
如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用<code>methodSignatureForSelector:</code>方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用<code>-forwardInvocation:</code>方法。
继续前面的例子,将<code>forwardingTargetForSelector</code>方法注释掉,添加<code>methodSignatureForSelector</code>和<code>forwardInvocation</code>方法的实现:
#pragma mark - Normal Forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding = [MessageForwarding new];
if ([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
示例代码 github。
三种方法的选择
Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢?
<ul>
<li>Method Resolution:由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。</li>
<li>Fast Forwarding:它可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。</li>
<li>Normal Forwarding:它跟Fast Forwarding一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。</li>
</ul>