1.2 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 messagea request to perform a particular actionto 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.

1.2.1 Structure of a Message

Message expressions in Objective-C are enclosed in square brackets.[2]

[2] This convention is known as infix syntax; it is borrowed from Smalltalk.

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.

1.2.1.1 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]];
1.2.1.2 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.

1.2.2 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:

  1. The runtime inspects the message's target object to determine the object's class.

  2. If the class contains an instance method with the same name as the message, the method is executed.

  3. 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.

  4. 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 in NSObject simply announces (with an error) that the object doesn't handle the message.

1.2.3 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.



    Part II: API Quick Reference