Messaging
Objects in Objective-C are largely autonomous, self-contained, opaque entities within the scope of a program. They are not passive containers for state behavior, nor data and a collection of functions that can be applied to that data. The Objective-C language reinforces this concept by allowing any message— a request to perform a particular action—to be passed to any object. The object is then expected to respond at runtime with appropriate behavior. In object-oriented terminology, this is called dynamic binding .
When an object receives a message at runtime, it can do one of three things:
Perform the functionality requested, if it knows how.
Forward the message to some other object that might know how to perform the action.
Emit a warning (usually stopping program execution), stating that it doesn’t know how to respond to the message.
A key feature here is that an object can forward messages that it doesn’t know how to deal with to other objects. This feature is one of the significant differences between Objective-C and other object-oriented languages such as Java and C++.
Dynamic binding, as implemented in Objective-C, is different than the late binding provided by Java and C++. While the late binding provided by those languages does provide flexibility, it comes with strict compile-time constraints and is enforced at link time. In Objective-C, binding is performed as messages are resolved to methods and is free from constraints until that time.
Structure of a Message
Message expressions in Objective-C are enclosed in square brackets.[2]
The expression consists of the
following parts: the object to which the message is sent (the
receiver), the message name, and optionally any arguments. For
example, the following message can be verbalized as
“send a play
message to the
object identified by the iPod
variable”:
[iPod play];
Any
arguments
in a message expression appear after colons in a message name. For
example, to tell the iPod
object to set the
volume, send it the following message:
[iPod setVolume:11];
If a message contains multiple arguments, the arguments are typically separated in the message name and follow colons after the corresponding component of the message. For example:
[iPod usePlaylist:@"Techno" shuffle:YES];
The name of this message is usePlaylist:shuffle
:.
The colons are part of the method name. If you
aren’t familiar with this syntax, it may appear a
bit odd at first. However, experience shows that structuring messages
this way helps code be more self-documenting than in languages such
as Java or C++ where parameters are lumped together without
appropriate labeling.
Nested messages
Messages can be nested so the return value from one message can become the receiver or parameter for another. For example, to assign the playlist for an iPod to play to the value of an iTunes playlist name without an intermediate variable, use the following:
[iPod usePlaylist:[iTunes currentPlaylist]];
Messaging nil
Messaging an
uninitialized
(or cleared) object variable (i.e., one with a
value of nil
) is not an error. If a message
doesn’t have a return value, nothing will happen. If
the message returns an object pointer, it will return
nil
. If the message returns a scalar value such as
an int
, it will return 0
.
Otherwise, the return value is unspecified.
How Messages Are Resolved into Methods
When a message is sent to an object, a search determines the implemented method that should be called. The logic of this search is:
The runtime inspects the message’s target object to determine the object’s class.
If the class contains an instance method with the same name as the message, the method is executed.
If the class does not have a method, the search is moved to the superclass. If a method with the same name as the message is found in the superclass, it is executed. This search is continued up the inheritance tree until a match is found.
If no match is found, the receiver object is sent the
forwardInvocation
: message. If the object implements this method, it has the dynamic ability to resolve the problem. This method’s default implementation inNSObject
simply announces (with an error) that the object doesn’t handle the message.
Selectors
While user-friendly names refer to methods in source code, the
runtime uses a much more efficient mechanism. At compile time, each
method is given a unique value of type SEL
called
a
selector. When the runtime performs the message
dispatch described in the previous section, it resolves the message
to a selector, which is then used to execute the method.
You can use selectors to indicate which method should be called on an
object. The following example shows how to use the
@selector
declaration to get a selector and
perform its method on an object:
SEL playSelector = @selector(play); [iPod performSelector:playSelector];
A selector identifies a method and is not associated with any
particular class. Assuming that a Child
class is
defined and implements a play method, the following would be valid:
[aChild performSelector:playSelector];
Using selectors directly can be helpful when you want to execute the same action on a collection of objects. For example, a case of iPod objects, held in an array, could all be told to play by sending the following message to the array:
[iPodArray makeObjectsPerformSelector:playSelector];
You will also see selectors in the Cocoa framework used in the
Target/Action paradigm. For more information about using selectors to
call methods on objects, see the NSInvocation
class documentation in Chapter 14.
Get Cocoa in a Nutshell now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.