Objective-C respondsToSelector

20

根据我目前所学:在Objective-C中,您可以向任何对象发送任何消息。如果对象实现了正确的方法,则该方法将被执行;否则,什么也不会发生。这是因为在发送消息之前,Objective-C会执行respondsToSelector

到目前为止我希望我的理解是正确的。

我写了一个小程序进行测试,在滑块移动时调用一个操作。同时为了测试,我将sender设置为NSButton,但实际上它是NSSlider。现在我询问对象是否会响应setAlternateTitle。当NSButton会响应而NSSlider不会响应。如果我运行代码并自己使用respondsToSelector进行测试,则会告诉我对象不会响应该选择器。如果我测试其他东西(如intValue),它将会响应。到目前为止,我的代码是正确的。

- (IBAction)sliderDidMove:(id)sender
{
    NSButton *slider = sender;

    BOOL responds =
    [slider respondsToSelector:@selector(setAlternateTitle)];

    if(responds == YES)
    {
        NSLog(@"YES");        
    }
    else
    {
        NSLog(@"NO");
    }

    [slider setAlternateTitle:@"Hello World"];
}

但是当我发送setAlternateTitle消息时,程序会崩溃,我不确定原因。难道不应该在发送消息之前使用respondsToSelector吗?

4个回答

158

首先,方法的名称(其选择器)包括所有子部分和冒号字符,如mvds所说。

其次,方法-respondsToSelector:并不是由运行时调用的,通常是由用户自己或API调用的(例如,要知道代理是否响应协议的可选方法)。

当您向一个对象发送消息时,运行时会在该对象的类中查找该方法的实现(通过对象的isa指针)。这相当于发送-respondsToSelector:,尽管消息本身没有被调度。如果在类或其超类中找到该方法的实现,则使用传递的所有参数调用它。

如果没有找到,那么运行时会给消息第二次执行的机会。它将从对象的类中发送消息+ (BOOL)resolveInstanceMethod:(SEL)name:此方法允许您在运行时向类中添加方法。如果此消息返回YES,则表示可以重新分派消息。

如果不行,则将消息第三次执行的机会交给- (id)forwardingTargetForSelector:(SEL)aSelector方法,并带有选择器。这个方法可以返回另一个对象,该对象可能能够代表实际接收者响应选择器。如果返回的对象能够响应,方法将被执行,并以原始消息返回值的形式返回。(注意:这仅适用于OS X 10.6或iOS 4以下的版本)。

如果返回的对象为nil或self(为了避免无限循环),运行时会第四次执行该方法...它发送消息 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 以获取方法签名,以便构建调用。 如果提供了一个,则通过消息 -(void)forwardInvocation:(NSInvocation *)anInvocation 发送一个调用。在此方法中,您可以解析调用并以任何方式构建要发送到其他目标的其他消息,然后您可以设置调用的返回值…该值将作为原始消息的返回值。
最后,如果对象没有返回方法签名,则运行时会向您的对象发送消息-(void)doesNotRecognizeSelector:(SEL)aSelector ,NSObject类中该方法的实现会抛出异常。

4
文档在这里:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html的一部分内容...其他的东西在NSObject类参考中。 - Psycho
1
顺便提一下,forwardingTargetForSelector: 只在 Mac OS X 10.6+ 或 iOS 4+ 中存在。 - user102008
9
您可以重写该方法以在处理消息时悄无声息地失败。苹果的文档明确表示,此方法必须始终导致抛出异常,因此静默失败违反了合同。参考链接:https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/doesNotRecognizeSelector: - user102008
抛出的异常会在iOS上导致应用程序崩溃,但在Mac OS X上不会,对吗? - Nicolas Miari
1
@user102008 - 这是非常好而且重要的注释,感谢分享。你应该得到更多的赞。我已经编辑了答案,包括这个注释。 - ArtOfWarfare
显示剩余2条评论

7

首先,selector不仅是消息的“名称”,而且包括其后的参数及其名称。

因此,-(void)setAlternateTitle:(NSString*)str 的正确选择器应为:

@selector(setAlternateTitle:)

with the :

关于您的问题:如果一个类respondsToSelector()并且您执行该选择器,则不应在发送未知选择器时崩溃。您在调试窗口中看到什么样的崩溃日志?

(ps. 为什么不将[slider setAlternateTitle:...]包含在if ( responds ) { ... }条件块中?)


更准确地说,选择器不包括“接下来的参数及其名称”,而是“消息名称中参数的数量和位置”。 - Slipp D. Thompson

2
“这是因为在消息发送之前,Objective-C会执行respondsToSelector。我觉得这不正确。如果对象不能响应选择器,则会在运行时崩溃。系统没有自动检查。如果运行时系统有检查,则我们永远不应该出现“未识别的选择器发送到实例”异常。如果我错了,请纠正我。”
“编辑:这不是一个直接的崩溃,但默认结果是进程将被终止。整个序列已经在评论和其他答案中解释过了,所以我不会再写一遍。”

2
它不会崩溃,而是退回到一个称为forwarding的过程。不幸的是,这似乎没有得到很好的文档记录,但它会执行几个操作:首先,在接收者的类上调用+resolveInstanceMethod:。如果失败,则调用-forwardingTargetForSelector:,然后调用-forwardInvocation:-forwardInvocation:的默认实现会引发未实现选择器异常,这与崩溃不同。 - Jens Ayton
@Ahruman,感谢您的解释。我之前不知道这个序列。虽然它不像分段错误那样直接导致崩溃,但默认结果似乎是进程将被终止。 - taskinoor
至少我是对的,这个方法不会被系统自动调用。 - taskinoor

2

有一个+instancesRespondToSelector:方法。顾名思义,它告诉你类的实例是否实现了该方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接