In Objective-C, “selector” has two meanings. It can be used to refer simply to the name of a method when it’s used in a source-code message to an object. It also, though, refers to the unique identifier that replaces the name when the source code is compiled. Compiled selectors are of type SEL
. All methods with the same name have the same selector. You can use a selector to invoke a method on an object—this provides the basis for the implementation of the target-action design pattern in Cocoa.
Methods and Selectors
Varying the Message at Runtime
The Target-Action Design Pattern
Avoiding Messaging Errors
For efficiency, full ASCII names are not used as method selectors in compiled code. Instead, the compiler writes each method name into a table, then pairs the name with a unique identifier that represents the method at runtime. The runtime system makes sure each identifier is unique: No two selectors are the same, and all methods with the same name have the same selector.
Compiled selectors are assigned to a special type, SEL
, to distinguish them from other data. Valid selectors are never 0
. You must let the system assign SEL
identifiers to methods; it’s futile to assign them arbitrarily.
The @selector()
directive lets you refer to the compiled selector, rather than to the full method name. Here, the selector for setWidth:height:
is assigned to the setWidthHeight
variable:
SEL setWidthHeight; |
setWidthHeight = @selector(setWidth:height:); |
It’s most efficient to assign values to SEL
variables at compile time with the @selector()
directive. However, in some cases, you may need to convert a character string to a selector at runtime. You can do this with the NSSelectorFromString
function:
setWidthHeight = NSSelectorFromString(aBuffer); |
Conversion in the opposite direction is also possible. The NSStringFromSelector
function returns a method name for a selector:
NSString *method; |
method = NSStringFromSelector(setWidthHeight); |
Compiled selectors identify method names, not method implementations. The display
method for one class, for example, has the same selector as display
methods defined in other classes. This is essential for polymorphism and dynamic binding; it lets you send the same message to receivers belonging to different classes. If there were one selector per method implementation, a message would be no different than a function call.
A class method and an instance method with the same name are assigned the same selector. However, because of their separate domains, there’s no confusion between the two. A class could define a display
class method in addition to a display
instance method.
The messaging routine has access to method implementations only through selectors, so it treats all methods with the same selector alike. It discovers the return type of a method, and the data types of its arguments, from the selector. Therefore, except for messages sent to statically typed receivers, dynamic binding requires all implementations of identically named methods to have the same return type and the same argument types. (Statically typed receivers are an exception to this rule, since the compiler can learn about the method implementation from the class type.)
Although identically named class methods and instance methods are represented by the same selector, they can have different argument and return types.
The performSelector:
, performSelector:withObject:
, and performSelector:withObject:withObject:
methods, defined in the NSObject
protocol, take SEL
identifiers as their initial arguments. All three methods map directly into the messaging function. For example,
[friend performSelector:@selector(gossipAbout:) |
withObject:aNeighbor]; |
is equivalent to:
[friend gossipAbout:aNeighbor]; |
These methods make it possible to vary a message at runtime, just as it’s possible to vary the object that receives the message. Variable names can be used in both halves of a message expression:
id helper = getTheReceiver(); |
SEL request = getTheSelector(); |
[helper performSelector:request]; |
In this example, the receiver (helper
) is chosen at runtime (by the fictitious getTheReceiver
function), and the method the receiver is asked to perform (request
) is also determined at runtime (by the equally fictitious getTheSelector
function).
Note: performSelector:
and its companion methods return an id
. If the method that’s performed returns a different type, it should be cast to the proper type. (However, casting doesn’t work for all types; the method should return a pointer or a type compatible with a pointer.)
In its treatment of user-interface controls, the Application Kit makes good use of the ability to vary both the receiver and the message.
NSControl
objects are graphical devices that can be used to give instructions to an application. Most resemble real-world control devices such as buttons, switches, knobs, text fields, dials, menu items, and the like. In software, these devices stand between the application and the user. They interpret events coming from hardware devices like the keyboard and mouse and translate them into application-specific instructions. For example, a button labeled “Find” would translate a mouse click into an instruction for the application to start searching for something.
The Application Kit defines a template for creating control devices and defines a few “off-the-shelf” devices of its own. For example, the NSButtonCell
class defines an object that you can assign to an NSMatrix
instance and initialize with a size, a label, a picture, a font, and a keyboard alternative. When the user clicks the button (or uses the keyboard alternative), the NSButtonCell
object sends a message instructing the application to do something. To do this, an NSButtonCell
object must be initialized not just with an image, a size, and a label, but with directions on what message to send and who to send it to. Accordingly, an NSButtonCell
instance can be initialized for an action message, the method selector it should use in the message it sends, and a target, the object that should receive the message.
[myButtonCell setAction:@selector(reapTheWind:)]; |
[myButtonCell setTarget:anObject]; |
The button cell sends the message using NSObject
’s performSelector:withObject:
method. All action messages take a single argument, the id
of the control device sending the message.
If Objective-C didn’t allow the message to be varied, all NSButtonCell
objects would have to send the same message; the name of the method would be frozen in the NSButtonCell
source code. Instead of simply implementing a mechanism for translating user actions into action messages, button cells and other controls would have to constrain the content of the message. This would make it difficult for any object to respond to more than one button cell. There would either have to be one target for each button, or the target object would have to discover which button the message came from and act accordingly. Each time you rearranged the user interface, you would also have to re-implement the method that responds to the action message. This would be an unnecessary complication that Objective-C happily avoids.
If an object receives a message to perform a method that isn’t in its repertoire, an error results. It’s the same sort of error as calling a nonexistent function. But because messaging occurs at runtime, the error often isn’t evident until the program executes.
It’s relatively easy to avoid this error when the message selector is constant and the class of the receiving object is known. As you write your programs, you can make sure that the receiver is able to respond. If the receiver is statically typed, the compiler performs this test for you.
However, if the message selector or the class of the receiver varies, it may be necessary to postpone this test until runtime. The respondsToSelector:
method, defined in the NSObject
class, determines whether a receiver can respond to a message. It takes the method selector as an argument and returns whether the receiver has access to a method matching the selector:
if ( [anObject respondsToSelector:@selector(setOrigin::)] ) |
[anObject setOrigin:0.0 :0.0]; |
else |
fprintf(stderr, "%s can’t be placed\n", |
[NSStringFromClass([anObject class]) UTF8String]); |
The respondsToSelector:
test is especially important when sending messages to objects that you don’t have control over at compile time. For example, if you write code that sends a message to an object represented by a variable that others can set, you should make sure the receiver implements a method that can respond to the message.
Note: An object can also arrange to have the messages it receives forwarded to other objects if it can’t respond to them directly itself. In that case, it appears that the object can handle the message, even though the object responds to the message indirectly by assigning it to another object. See Message Forwarding in Objective-C 2.0 Runtime Programming Guide for more information.
© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)