Objective-C respondsToSelector
Asked Answered
P

4

20

From what I have learned so far: In Objective-C you can send any message to any object. If the object does implement the right method it will be executed otherwise nothing will happen. This is because before the message is sent Objective-C will perform respondsToSelector.

I hope I am right so far.

I did a little program for testing where an action is invoked every time a slider is moved. Also for testing I set the sender to NSButton but in fact it is an NSSlider. Now I asked the object if it will respond to setAlternateTitle. While a NSButton will do and NSSlider will not. If I run the code and do respondsToSelector myself it will tell me the object will not respond to that selector. If I test something else like intValue, it will respond. So my code is fine so far.

- (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"];
}

But when I actually send the setAlternateTitle message the program will crash and I am not exactly sure why. Shouldn't it do a respondsToSelector before sending the message?

Proceleusmatic answered 1/1, 2011 at 15:11 Comment(0)
P
158

First of all, the name of a method (its selector) includes all subparts and colon characters, as mvds said.

Second of all, the method -respondsToSelector: is not called by the runtime, it's usually called by the user (yourself or APIs that want to know if a delegate, for example, responds to an optional method of the protocol).

When you send a message to an object, the runtime will look for the implementation of the method in the class of the object (through the object's isa pointer). It's equivalent to sending -respondsToSelector: although the message itself is not dispatched. If the implementation of the method is found in the class or in its superclasses, it's called with all the arguments you passed in.

If not, then the runtime gives the message a second chance to be executed. It will start by sending the message + (BOOL)resolveInstanceMethod:(SEL)name to the class of the object: this method allows you to add the method at runtime to the class: if this message returns YES, it means it can redispatch the message.

If not it gives the message a third chance to be executed, it sends - (id)forwardingTargetForSelector:(SEL)aSelector with the selector, this method can return another object that may be able to respond to the selector on behalf of the actual receiver, if the returned object can respond, the method is executed and the value is returned as if it was returned by the original message. (Note: This is available beginning with OS X 10.6 or iOS 4.)

If the returned object is nil or self (to avoid infinite loops), the runtime gives the message a fourth chance to execute the method… It sends the message - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector to get a method signature in order to build an invocation. If one is provided then an invocation is sent through the message - (void)forwardInvocation:(NSInvocation *)anInvocation. In this method you can parse the invocation and build other messages to send to other targets in any ways you want, and then you can set the return value of the invocation… That value will act as the return value of the original message.

Finally, if no method signature is returned by the object, then the runtime sends the message - (void)doesNotRecognizeSelector:(SEL)aSelector to your object, the implementation of this method in NSObject class throws an exception.

Propinquity answered 1/1, 2011 at 15:12 Comment(7)
+1 for this excellent explanation. Can you provide some link where this whole process is documented in detail. It is worth reading.Pricilla
The docs are here: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/… in part... Other stuff are in NSObject class referencePropinquity
by the way, forwardingTargetForSelector: is only in Mac OS X 10.6+ or iOS 4+Monger
"You can override this method to silently fail at handling messages." Apple's docs specifically say that this method must always result in an exception being thrown, so failing silently violates the contract: developer.apple.com/library/mac/#documentation/Cocoa/Reference/…:Monger
The exception thrown crashes the app on iOS, but not on Mac OS X, right?Antidepressant
@Monger - That's a very good and important note, thanks for sharing. You deserve more upvotes for it. I've edited the answer to include that note.Epidaurus
* * B R A V O ! * *Lenes
B
7

For one thing, the selector is not only the "name" of the message, but also what follows, i.e. the arguments, and their names.

So the correct selector for some -(void)setAlternateTitle:(NSString*)str would be

@selector(setAlternateTitle:)

with the :

As for your problem: If a class respondsToSelector() and you perform that selector, you shouldn't get a crash on sending an unknown selector. What kind of crash log do you see in the debugging window?

(ps. why not include the [slider setAlternateTitle:...] in the if ( responds ) { ... } conditional block?)

Bossism answered 1/1, 2011 at 15:19 Comment(1)
More accurately, a selector does not include “what follows, i.e. the arguments, and their names”, but “the number and position of arguments within the message name”.Myrtismyrtle
P
2

"This is because before the message is sent Objective-C will perform respondsToSelector."

I guess this is not correct. If the object does not respond to selector, it will crash at runtime. There is no automatic checking by the system. If there was a check by the run time system then we should never get "unrecognized selector sent to instance" exception.

Please make me correct if I am wrong.

EDIT: This is not a straight forward crash, but the default result is the process will be terminated. The whole sequence is already explained in comment and other answer, so I am not going to write that again.

Pricilla answered 1/1, 2011 at 15:23 Comment(3)
It doesn’t crash, it falls back to a process called forwarding. Unfortunately, this doesn’t appear to be well documented, but it does several things: first it calls +resolveInstanceMethod: on the receiver’s class. If this fails, it calls -forwardingTargetForSelector:, then -forwardInvocation:. The default implementation of -forwardInvocation: throws an unimplemented selector exception, which isn’t the same thing a crash.Sinaloa
@Ahruman, thanks for the explanation. I didn't knew this sequence. It is not really a straight forward crash like segmentation fault, but the default result seems the process will be terminated.Pricilla
At least I was right that the method is not automatically called by the system.Pricilla
A
2

There is an +instancesRespondToSelector: method. As the name suggests, it tells you whether the instances of the class implement that method.

Antidepressant answered 8/9, 2011 at 6:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.