What You’ll Learn in This Hour
Understanding the target-action pattern
Using SEL and @selector
Invoking selectors with NSInvocation
Managing messages with runtime tools
By now, you should be familiar with Objective-C being described as being a dynamic messaging environment, and you have seen examples of messages in Hour 3, “Using Object-Oriented Features in Objective-C,” and in Hour 6, “Exploring Messaging and a Testbed App.” This hour looks inside the messages to see how they work. Because this is the heart of Objective-C and also because this is a significant way in which it differs from many other object-oriented programming languages, it is worthwhile to spend some time on the concepts.
In the simple examples shown in Hours 3 and 6, you saw messages that were used somewhat in the way in which functions are used in other languages, although, as noted, messaging is very different from calling functions. The similarity in the examples is that they are sent at a specific place in the code. In Objective-C, you send a message at a certain point in the program’s execution, and in other languages at that point you call a function.
However, in Objective-C, you can turn a message into an object that can then be executed directly, but you can also send that object to an object such as an instance of an NSTimer
that takes care of invoking it at appropriate times. By being able to in effect convert the message to a reusable object, you have a tremendous degree of flexibility. Furthermore, the object contains properties that you can set and reset as the need arises. You see how to do this in the course of this hour.
GO TO A related use of messages is found in Hour 22, “Grand Central Dispatch: Using Queues and Threading,” page 283.
In Hour 3, you saw simple examples of messages such as the following:
myVariable = [myObject init];
The high-level description of messaging in Hour 3 identifies the right side of the expression as the message; the result of the message is used in the expression to set the value of myVariable
.
Now it is time to look inside the message and to name the two words shown in this example:
Receiver is the object to which the message is sent (myObject
, in this case).
Selector is the command sent to the receiver (init
, in this case).
At runtime, the selector is used to identify the specific method of the object that is to be performed. This distinction between the selector and the method of the receiver that it selects at runtime helps to provide much of the flexibility and dynamism of Objective-C. It is made possible by the Objective-C runtime, which is the topic of the following section.
The flexibility described in the previous section is at the heart of many debates, but, as is so often the case, advances in processor speed and memory availability have made many of those arguments much less relevant today. The issue is that when a function call is coded in a statically typed language, the compiler can check to make certain that the object that is asked to perform the function actually has such a function available. With the dynamic messaging environment, the message (specifically the selector) is sent to a receiver, and it is quite possible that the receiver will not be able to perform the method.
In many cases (particularly if the receiver is declared to be of type id
, meaning any object), there is no way to perform this check at compile time. At its simplest, the argument has been whether this error checking should be done once at compile time or every time a message is sent at runtime. With the advances on the hardware side and several decades of experience optimizing the dispatching of methods in response to selectors, the flexibility of the messaging model in Objective-C has won out at least in the OS X and iOS environments.
Note: The Runtime
The shared library that implements the runtime component of Objective-C programs is commonly referred to simply as the runtime.
Here, for example, is the function call that is generated by the compiler on OS X when it encounters a message call in your code:
id objc_msg (id theReceiver, SEL theSelector, ...)
You can see that it is dynamic: theReceiver
is a generic id
type, as is the result of the function. theSelector
and the SEL
type are described in the following section.
Although you rarely use the shared library directly (the most common reason is if you are programming cross-language projects), it is there when your app is running and, at runtime, becomes a part of your app. This does not mean that you can deal with all of your app’s bugs by blaming them on the runtime code. In fact, if it is involved with bugs at all, it is most likely an innocent bystander.
Rather, you need to remember that if you are coming from compiler-centric languages, you are gaining a tremendous degree of flexibility with Objective-C and its runtime, but you are also losing a safety net that compilers with strict type checking and compile time can provide.
There are two consequences of this that you should remember:
More than ever before, it is important to catch and correct errors at the earliest possible opportunity. Xcode helps you with its tools to catch syntax errors based on the underlying Objective-C syntax as well as catching some errors that would only be caught at compile time in other environments. Many people are used to writing code and then debugging it in a second step, but today it is common practice to merge those processes into one step. (This is true for other modern programming languages as well as Objective-C.)
You are not at the mercy of what happens at runtime. There are methods you can call to test if a message you are about to send will generate an error or not. They are described later in this hour.
Just as there have been concerns about memory management in object-oriented programs, there have also been concerns about performance. As noted, one of the key areas in which performance can be compromised is in the selection and dispatching of methods. Developers of the Objective-C language and its compilers have focused on this area. Method names for a program are stored in a table where they can be looked up as needed. Each is associated with a unique identifier, and it is that unique identifier that is used for dispatching. This avoids handling long strings of characters to identify methods that are (properly) labeled descriptively.
You can gain access to these identifiers indirectly and can not only optimize your code, but also add some additional dynamism to it. To accomplish this, a special type, SEL
, has been created. You use it to type identifiers just as you would type an identifier as an int
or float
.
But what value can you assign to a SEL
? The answer is simple: You assign to a SEL
identifier the selector you would use in a message. There are two variations on the way that you do this; they differ in the way they handle arguments to the selector. Both of these mechanisms are described in the sections that follow.
Furthermore, you can use both mechanisms in NSTimer
methods where you set up a timer to run repeatedly.
Tip: Compiled Versus Computed Selectors
When you write a message such as the following
myVariable = [myObject init];
you specify the receiver and the selector. As is the case with the other code that you write, the compiler generates the code that runs. The selector is referred to as a compiled selector.
By contrast, you can use a selector that is evaluated at runtime from a string that can be generated on the fly. This is referred to as a computed selector.
For zero, one, or two arguments, you create a SEL
that you can subsequently pass into one of the performSelector
methods. The performSelector
methods are further limited in that their one or two arguments must be of type id
. Even with these limitations, they handle many (perhaps most) of the selectors you use in common processing.
There are several ways of creating a compiled selector. The simplest way is to use the @selector
compiler directive. If that is the route you take, you just type in the code you would have used in a message. Thus, for a message such as
myVariable = [myObject myAction];
you can generate a compiled selector with
SEL mySelector;
mySelector = @selector(myAction);
If you have arguments for the method that you are calling, you supply their keywords but not their values, as follows:
mySelector = @selector(myAction: forPurpose:);
If there are two arguments, you supply both of them, as follows:
mySelector = @selector(myAction: forPurpose: withCondition:);
Instead of hard-coding the selector in your code, you might want to set it from a variable string at runtime. (These are computed selectors.) There is a utility function for that. The first example in the previous section can be rewritten as the following:
NSString * myString;
SEL mySelector;
// set myString from user input, options, a data store, etc.
mySelector = NSSelectorFromString (myString);
If you are storing a selector’s string for reuse at a later time, you can convert a selector to a string:
myString = NSStringFromSelector (myAction: forPurpose:);
After you have a selector, you can perform it with any of these three methods (they are declared in the NSObject
protocol so they are available to any Cocoa classes):
– (id)performSelector:(SEL)aSelector;
– (id)performSelector:(SEL)aSelector withObject:(id)anObject;
– (id)performSelector:(SEL)aSelector withObject:(id)anObject
withObject:(id)anotherObject;
If you use the second or third version, the selector must take parameters of type id
. If you want to use more parameters or if you want to use a selector that does not take id
parameters, you use NSInvocation
, as described in the following section.
performSelector
is used for the selector in a message, as follows:
[receiver performSelector:mySelector];
If you are using parameters (the second or third version), you pass the id
in the objects and the message is put together with the keywords and parameters.
Tip
Just remember the colon before each parameter in the @selector
directive. Leaving it out is a very common mistake.
These methods are declared in the NSObject
protocol. Related methods are declared directly in NSObject
, the root class of Cocoa.
Note
The NSObject performSelector
methods are listed here so that you can get a sense of the opportunities you have in the Cocoa framework, which itself is beyond the scope of this book.
– performSelector:withObject:afterDelay:
– performSelector:withObject:afterDelay:inModes:
– performSelectorOnMainThread:withObject:waitUntilDone:
– performSelectorOnMainThread:withObject:waitUntilDone:modes:
– performSelector:onThread:withObject:waitUntilDone:
– performSelector:onThread:withObject:waitUntilDone:modes:
– performSelectorInBackground:withObject:
+ cancelPreviousPerformRequestsWithTarget:
+ cancelPreviousPerformRequestsWithTarget:selector:object:
In addition, you can use the NSTimer
subclass of NSObject
to perform selectors.
Using a Selector with Interface Builder
For three or more arguments, you use an NSInvocation
object. In addition to being able to handle more arguments, you are not limited to using arguments of type id
as long as they are classes (in other words, no ints
or chars
). With performSelector
, you specify the receiver and action as arguments to performSelector
. NSInvocation
is a more sophisticated mechanism in which you convert a message to an object. NSInvocation
objects have properties that specify the target, selector, arguments, and return value. This flexibility lets you use NSInvocation
objects with much more flexibility than when you use performSelector
.
There are situations in which a common use of NSInvocation
instances is to use and reuse the same selector a number of times in different contexts. You can, for example, set up a selector and vary its target (that is, the receiver of the message). You can also change arguments. However, the one thing you cannot change is the method signature of the selector. That is set when you create the object, and it cannot be varied.
Method Signatures
In Objective-C, a method signature consists of the method name along with parameter names and colons (if needed). Thus, you can have insertObject:atIndex:
, which is a method that takes two parameters.
Although NSInvocation
objects are more flexible than performSelector
, in practice you might not need them very often because the simpler interface of performSelector
handles many of the common cases. If you do want to use an NSInvocation
object, it is summarized in this section. There are three sets of methods that enable you to do the following:
Create an NSInvocation
instance.
Set and get its properties.
Invoke the instance (that is, perform the selector).
Although you can change every property of an NSInvocation
instance, you cannot change the message signature of the instance you have created. You create it using the class method shown here:
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)signature
After you have your NSInvocation
instance, you set its properties. You can use a newly created instance, or you can reuse an existing one if it is created for the right message signature. This is most commonly done if you want to reinvoke the invocation with a minor modification of its target or arguments or at a different time. The following sections cover the setters you use.
GO TO Hour 16, “Managing Memory and Runtime Objects,” p. 221, for more information on memory aspects of NSInvocation
.
You use setTarget
to set the target. Here is the declaration:
– (void)setTarget:(id)anObject
Following on from the NSInvocation
created in the Try It Yourself activity, if you have an instance of MyClass
called myInstance
, you can write:
[myInvocation setTarget:myInstance];
The corresponding getter is:
– (id)target
You can set the selector as long as it is compatible with the message signature you used in creating the NSInvocation
instance. You use setSelector
; here is the declaration:
– (void)setSelector:(SEL)selector
You frequently see the same selector used here as was used in creating the message signature. That certainly ensures compatibility because they are identical, but as long as they are compatible, you are fine:
[myInvocation setSelector:@selector(myMethod:)];
The corresponding getter is:
– (SEL)selector
The arguments for the NSInvocation
instance are in a buffer. The zeroth element is the target, and the first is the selector. You use the accessors just described to access those, so you start working with element 2 of the array. Here is the declaration to set an argument:
– (void)setArgument:(void *)buffer atIndex:(NSInteger)index
When you are setting an argument to an object, remember to pass in a pointer to it, as shown in the following line of code:
[myInvocation setArgument:&myObjectForArgument atIndex:2];
The corresponding getter is:
– (void)getArgument:(void *)buffer atIndex:(NSInteger)index
Remember that you pass in a pointer so that to retrieve the same argument set in the previous code, you write:
[myInvocation getArgument:&anArray atIndex:2];
When you send a message in your code, you receive the result back and can assign it to a variable, or you can use it in an expression. When you are invoking a message at some time in the future, you have to retrieve the result from the invocation. Here is the getter:
– (void)getReturnValue:(void *)buffer
You have two ways of using this getter: one for objects and another one for C variables other than pointers and objects.
In the case of an object, you declare it in the code where you want to access the result just as you normally would. You then get it from the NSInvocation
and pass in a pointer to your variable as in this code:
[myInvocation getReturnValue:&myLocallyDeclaredVariable];
If the result is not an object, you allocate a buffer of the right size and pass that in. You can get the size of the returned value by asking the method signature. And, because you used the method signature in creating your NSInvocation
instance, you can ask that instance for the signature. Here is an example:
NSUInteger length = [[myInvocation methodSignature] methodReturnLength];
buffer = (void *)malloc(length);
[myInvocation getReturnValue:buffer];
After you have your instance of NSInvocation
and have set its target, selector, and arguments, you can invoke it with the following:
– (void)invoke
You simply write:
[myInvocation invoke];
You can short-circuit the process by using the following:
– (void)invokeWithTarget:(id)anObject
This means you can write:
[myInvocation invokeWithTarget: myInstance];
In this case, you need to set the selector and the arguments, but you do not need to set the target separately.
In addition to these methods, you can use the NSTimer
subclass of NSObject
to invoke NSInvocation
instances.
As noted previously, the messaging structure in Objective-C is implemented in large part in the runtime. This provides a great deal of flexibility, but it also means that you can generate errors at runtime that, in other languages, would have been caught at compile time. This is not merely a matter of the error appearing at a different time. It means in most cases that the error is seen by the end user rather than by the developer.
To avoid such circumstances, you can check to see if a class can respond to a selector. Here is the declaration:
– (BOOL)respondsToSelector:(SEL)aSelector
You can write:
BOOL myBool = [self.myCurrencyConverter respondsToSelector:@selector (calculate);
or
BOOL myBool = [self.myCurrencyConverter respondsToSelector: mySelector];
This is declared in the NSObject
protocol, so it is available for all instances of all classes.
A companion class method lets you test a class:
+ (BOOL)instancesRespondToSelector:(SEL)aSelector
Messages are at the core of Objective-C and represent one of the biggest differences between it and some other object-oriented programming languages. In this hour, you have examined how messages are constructed as well as how you can construct them yourself in code. You have also seen how you can perform and invoke them not only when you need to, but also as prepackaged messages that can be sent as the need arises.
Q. How do you choose between using performSelector and using NSInvocation?
A. performSelector
works for messages with zero, one, or two arguments. Also, they must be of type id
and must be objects.
Q. What is the runtime?
A. It is the data structures and functions that implement the runtime dynamic features of Objective-C. It is not optional, but it’s there for you and you rarely need to worry about it. It is written mostly in C and Assembler. It provides the underpinnings of Objective-C but is written in previous languages.
1. How do you make certain that a message you are sending is valid?
2. NSInvocation
converts an Objective-C message to what?
1. Package it in a selector and test it against the receiver with respondsToSelector
.
2. NSInvocation
converts an Objective-C message to an object.
Explore the sample code on developer.apple.com to see how you can use selectors. Good choices are the following:
Use any of the Xcode storyboard templates on iOS and search for the code that implements bar button items. (Often it is in viewDidLoad
.) You’ll see how selectors are attached to these buttons.
Look at the MenuMadness
sample code to see how selectors are attached to menu commands on OS X.
3.142.12.207