< Previous PageNext Page >

Object-Oriented Programming With Objective-C

Cocoa is pervasively object-oriented, from its paradigms and mechanisms to its event-driven architecture. Objective-C, the primary development language for Cocoa, is thoroughly object-oriented too, despite its grounding in ANSI C. It provides runtime support for message dispatch and specifies syntactical conventions for defining new classes. Objective-C supports most of the abstractions and mechanisms found in other object-oriented languages such as C++ and Java. These include inheritance, encapsulation, reusability, and polymorphism.

But Objective-C is different from these other object-oriented languages, often in important ways. For example, Objective-C, unlike C++, doesn’t allow operator overloading, templates, or multiple inheritance. Objective-C also doesn’t have a “garbage collection” mechanism—such as Java’s—that automatically frees unneeded objects (although it has mechanisms and conventions for accomplishing the same thing).

Although Objective-C doesn’t have these features, its strengths as an object-oriented programming language more than compensate. What follows is an exploration of the special capabilities of Objective-C as well as an overview of the Cocoa version of the Java programming language.

Further Reading: Much of this section summarizes information from the definitive guide to Objective-C, The Objective-C Programming Language. Consult this document for a detailed and comprehensive description of Objective-C.

In this section:

The Objective-C Advantage
Using Objective-C


The Objective-C Advantage

If you’re a procedural programmer new to object-oriented concepts, it might help at first to think of an object as essentially a structure with functions associated with it. This notion is not too far off the reality, particularly in terms of runtime implementation.

Every Objective-C object hides a data structure whose first member—or instance variable—is the “isa pointer.” (Most remaining members are defined by the object’s class and superclasses.) The isa pointer, as the name suggests, points to the object’s class, which is an object in its own right (see Figure 2-1) and is compiled from the class definition. The class object maintains a dispatch table consisting essentially of pointers to the methods it implements; it also holds a pointer to its superclass, which has its own dispatch table and superclass pointer. Through this chain of references, an object has access to the method implementations of its class and all its superclasses (as well as all inherited public and protected instance variables). The isa pointer is critical to the message-dispatch mechanism and to the dynamism of Cocoa objects.


Figure 2-1  An object’s isa pointer

An object’s isa pointer

This peek behind the object facade gives a highly simplified view of what happens in the Objective-C runtime to enable message-dispatch, inheritance, and other facets of general object behavior. But this information is essential to understanding the major strength of Objective-C, its dynamism.

Dynamism

Objective-C is a very dynamic language. Its dynamism frees a program from compile-time and link-time constraints and shifts much of the responsibility for symbol resolution to runtime, when the user is in control. Objective-C is more dynamic than other programming languages because its dynamism springs from three sources:

For dynamic typing, Objective-C introduces the id data type, which can represent any Cocoa object. A typical use of this generic object type is shown in this part of the code example from Listing 2-2:

id word;
while (word = [enm nextObject]) {
// etc....

The id data type makes it possible to substitute any type of object at runtime. You can thereby let runtime factors dictate what kind of object is to be used in your code. Dynamic typing permits associations between objects to be determined at runtime rather than forcing them to be encoded in a static design. Static type checking at compile time may ensure stricter data integrity, but in exchange for that integrity, dynamic typing gives your program much greater flexibility. And through object introspection (for example, asking a dynamically typed, anonymous object what its class is) you can still verify the type of an object at runtime and thus validate its suitability for a particular operation. (Of course, you can always statically check the types of objects when you need to.)

Dynamic typing gives substance to dynamic binding, the second kind of dynamism in Objective-C. Just as dynamic typing defers the resolution of an object’s class membership until runtime, dynamic binding defers the decision of which method to invoke until runtime. Method invocations are not bound to code during compilation, but only when a message is actually delivered. With both dynamic typing and dynamic binding, you can obtain different results in your code each time you execute it. Runtime factors determine which receiver is chosen and which method is invoked.

The runtime’s message-dispatch machinery enables dynamic binding. When you send a message to a dynamically typed object, the runtime system uses the receiver’s isa pointer to locate the object’s class, and from there the method implementation to invoke. The method is dynamically bound to the message. And you don’t have to do anything special in your Objective-C code to reap the benefits of dynamic binding. It happens routinely and transparently every time you send a message, especially one to a dynamically typed object.

Dynamic loading, the final type of dynamism, is a feature of Cocoa that depends on Objective-C for runtime support. With dynamic loading, a Cocoa program can load executable code and resources as they’re needed instead of having to load all program components at launch time. The executable code (which is linked prior to loading) often contains new classes that become integrated into the runtime image of the program. Both code and localized resources (including nib files) are packaged in bundles and are explicitly loaded with methods defined in Foundation’s NSBundle class.

This “lazy-loading” of program code and resources improves overall performance by placing lower memory demands on the system. Even more importantly, dynamic loading makes applications extensible. You can devise a plug-in architecture for your application that allows you and other developers to customize it with additional modules that the application can dynamically load months or even years after the application is released. If the design is right, the classes in these modules will not clash with the classes already in place because each class encapsulates its implementation and has its own name space.

Language Extensions

Objective-C features two extensions to the base language that are powerful tools in software development: categories and protocols. Both extensions introduce different techniques for declaring methods and associating them with a class.

Categories

Categories give you a way to add methods to a class without having to make a subclass. The methods in the category become part of the class type (within the scope of your program) and are inherited by all the class’s subclasses. There is no difference at runtime between the original methods and the added methods. You can send a message to any instance of the class (or its subclasses) to invoke a method defined in the category.

Categories are more than a convenient way to add behavior to a class. You can also use categories to compartmentalize methods, grouping related methods in different categories. Categories can be particularly handy for organizing large classes; you can even put different categories in different source files if, for instance, there are several developers working on the class.

You declare and implement a category much as you do a subclass. Syntactically, the only difference is the name of the category, which follows the @interface or @implementation directive and is put in parentheses. For example, say you want to add a method to the NSArray class that prints the description of the collection in a more structured way. In the header file for the category, you would write declaration code similar to the following:

#import <Foundation/NSArray.h> // if Foundation not already imported
 
@interface NSArray (PrettyPrintElements)
- (NSString *)prettyPrintDescription;
@end

Then in the implementation file you’d write code such as:

#import "PrettyPrintCategory.h"
 
@implementation NSArray (PrettyPrintElements)
- (NSString *)prettyPrintDescription {
    // implementation code here...
}
@end

There are some limitations to categories. You cannot use a category to add any new instance variables to the class. Although a category method can override an existing method, it is not recommended that you do so, especially if you want to augment the current behavior. One reason for this caution is that the category method is part of the class’s interface, and so there is no way to send a message to super to get the behavior already defined by the class. If you need to change what an existing method of a class does, it is better to make a subclass of the class.

You can define categories that add methods to the root class, NSObject. Such methods are available to all instances and class objects that are linked into your code. Informal protocols—the basis for the Cocoa delegation mechanism—are declared as categories on NSObject. This wide exposure, however, has its dangers as well as its uses. The behavior you add to every object through a category on NSObject could have consequences that you might not be able to anticipate, leading to crashes, data corruption, or worse.

Protocols

The Objective-C extension called a protocol is very much like an interface in Java. Both are simply a list of method declarations publishing an interface that any class can choose to implement. The methods in the protocol are invoked by messages sent by an instance of some other class.

The main value of protocols is that they, like categories, can be an alternative to subclassing. They yield some of the advantages of multiple inheritance in C++, allowing sharing of interfaces (if not implementations). A protocol is a way for a class to declare an interface while concealing its identity. That interface may expose all or (as is usually the case) only a range of the services the class has to offer. Other classes throughout the class hierarchy, and not necessarily in any inheritance relationship (not even to the root class), can implement the methods of that protocol and so access the published services. With a protocol, even classes that have no knowledge of another’s identity (that is, class type) can communicate for the specific purpose established by the protocol.

There are two types of protocols: formal and informal. Informal protocols were briefly introduced in “Categories”. These are categories on NSObject; as a consequence, every object with NSObject as its root object (as well as class objects) implicitly adopts the interface published in the category. Unlike formal protocols, a class does not have to implement every method in an informal protocol, just those methods it’s interested in. For an informal protocol to work, the class declaring the informal protocol must get a positive response to a respondsToSelector: message from a target object before sending that object the protocol message. (If the target object did not implement the method there would be a runtime exception.)

Formal protocols are usually what is designated by “protocol” in Cocoa. They allow a class to formally declare a list of methods that are an interface to a vended service. The Objective-C language and runtime system supports formal protocols; the compiler can check for types based on protocols, and objects can introspect at runtime to verify conformance to a protocol. Formal protocols have their own terminology and syntax. The terminology is different for provider and client:

Both the declaration and the adoption of a protocol have their own syntactical forms in Objective-C. To declare a protocol you must use the @protocol compiler directive. The following example shows the declaration of the NSCoding protocol (in the Foundation framework’s header file NSObject.h).

@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
@end

The declaring class needs not implement these methods, but should invoke them in the conforming object.

A class adopts a protocol by specifying the protocol, enclosed by angle brackets, at the end of its @interface directive, just after the superclass. A class can adopt multiple protocols by delimiting them with commas. This is how the Foundation NSData class adopts three protocols.

@interface NSData : NSObject <NSCopying, NSMutableCopying, NSCoding>

By adopting these protocols, NSData commits itself to implementing all methods declared in the protocols. Categories can also adopt protocols, and their adoption becomes part of the definition of their class.

Objective-C types classes by the protocols they conform to as well as the classes they inherit from. You can check if a class conforms to a particular protocol by sending it a conformsToProtocol: message:

if ([anObject conformsToProtocol:@protocol(NSCoding)]) {
        // do something appropriate
}

In a declaration of a type—a method, instance variable, or function—you can specify protocol conformance as part of the type. You thus get another level of type checking by the compiler, one that’s more abstract because it’s not tied to particular implementations. You use the same syntactical convention as for protocol adoption: Put the protocol name between angle brackets to specify protocol conformance in the type. You often see the dynamic object type, id, used in these declarations, for example:

- (void)draggingEnded:(id <NSDraggingInfo>)sender;

Here the object referred to in the argument can be of any class type, but it must conform to the NSDraggingInfo protocol.

Cocoa provides several examples of protocols other than the ones shown or so far. An interesting one is the NSObject protocol. Not surprisingly, the NSObject class adopts it, but so does the other root class, NSProxy. Through the protocol, the NSProxy class can interact with the parts of the Objective-C runtime essential to reference counting, introspection, and other basic aspects of object behavior.

Formal protocols have their limitations. If the list of methods declared in a protocol grows over time, adopters of the protocol would become non-conforming. So formal protocols in Cocoa are used for sets of methods that are stable, such as NSCopying and NSCoding. If you anticipate growth in the set of protocols methods, declare informal protocols instead of formal protocols.

Using Objective-C

The way work gets done in an object-oriented program is through messages; one object sends a message to another object. Through the message, the sending object requests something from the receiving object (receiver). It requests that the receiver perform some action, return some object or value, or do both things.

Objective-C adopts a unique syntactical form for messaging. Take the following statement from the SimpleCocoaTool code in Listing 2-2:

NSEnumerator *enm = [sorted_args objectEnumerator];

The message expression is on the right side of the assignment, enclosed by the square brackets. The left-most item in the message expression is the receiver, a variable representing the object to which the message is sent. In this case, the receiver is sorted_args, an instance of the NSArray class. Following the receiver is the message proper, in this case objectEnumerator. (For now, we are going to focus on message syntax and not look too deeply into what this and other messages in SimpleCocoaTool actually do.) The message objectEnumerator invokes a method of the sorted_args object named objectEnumerator, which returns a reference to an object that is held by the variable enm on the left side of the assignment. This variable is statically typed as an instance of the NSEnumerator class. You can diagram this statement as:


Object messaging syntax

Messages often have parameters, or arguments. A message with a single argument affixes a colon to the message name and puts the argument right after the colon:


Object messaging with argument

As with function parameters, the type of an argument must match the type specified in the method declaration. Take as an example the follow message expression from SimpleCocoaTool:

NSCountedSet *cset = [[NSCountedSet alloc] initWithArray:args];

Here args, which is also an instance of the NSArray class, is the argument of the message named initWithArray:.

If a message has multiple arguments, the message name has multiple segments; each segment ends with a colon, and an argument follows the colon:


Object messaging with multiple arguments

The initWithArray: example cited above is interesting in that it illustrates nesting. With Objective-C, you can nest one message inside another message; the object returned by one message expression is used as the receiver by the message expression that encloses it. So to interpret nested message expressions, start with the inner expression and work your way outward. The interpretation of the above statement would be:

  1. The alloc message is sent to the NSCountedSet class, which creates (by allocating memory for it) an uninitialized instance of the class.

    Note: Objective-C classes are objects in their own right and you can send messages to them as well as to their instances. In a message expression, the receiver of a class message is always a class object.

  2. The initWithArray: message is sent to the uninitialized instance, which initializes itself with the array args and returns a reference to itself.

Next consider this statement from the main routine of SimpleCocoaTool:

NSArray *sorted_args = [[cset allObjects] sortedArrayUsingSelector:@selector(compare:)];

What’s noteworthy about this message expression is the argument of the sortedArrayUsingSelector: message. This argument requires the use of the @selector compiler directive to create a selector. A selector is a name with which the Objective-C runtime uniquely identifies a method of the receiver’s. It includes all parts of the message name, including colons, but does not include anything else, such as return type or parameter types.

Let’s pause a moment to review message and method terminology. A method is essentially a function defined and implemented by the class of which the receiver of a message is a member. A message, which is a selector combined with its arguments, is sent to a receiver and this results in the invocation (or execution) of the method. A message expression encompasses both receiver and message. Figure 2-2 depicts these relationships.


Figure 2-2  Message terminology

Message terminology

Objective-C uses a number of defined types and literals that you won’t find in ANSI C. In some cases, these types and literals replace their ANSI C counterparts. Table 2-1 describes a few of the important ones, including the allowable literals for each type.

Table 2-1  Important Objective-C defined types and literals

Type

Description and literal

id

The dynamic object type. Its negative literal is nil.

Class

The dynamic class type. Its negative literal is Nil.

SEL

The data type (typedef) of a selector. As in ANSI C, the negative literal of such types is NULL.

BOOL

A boolean type. The literal values are YES and NO.

In your program’s control-flow statements, you can test for the presence (or absence) of the appropriate negative literal to determine how to proceed. For example, the following while statement from the SimpleCocoaTool code implicitly tests the word object variable for the presence of a returned object (or, in another sense, the absence of nil):

while (word = [enm nextObject]) {
    printf("%s\n", [word UTF8String]);
}

In Objective-C, you can often send a message to nil with no ill effects. Return values from messages sent to nil are guaranteed to work as long as what is returned is typed as an object.

One final thing to note about the SimpleCocoaTool code is something that is not readily apparent if you’re new to Objective-C. Compare this statement:

NSEnumerator *enm = [sorted_args objectEnumerator];

with this one:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

On the surface, they seem to do identical things; both return a reference to an object. However there is an important semantic difference that has to do with the ownership of the returned object, and hence the responsibility for freeing it. In the first statement, the SimpleCocoaTool program does not own the returned object. In the second statement, the program creates the object and so owns it. The last thing the program does is to send the release message to the created object, thus freeing it. The only other explicitly created object (the NSCountedSet instance) is also explicitly released at the end of the program. For a summary of the policy for object ownership and disposal, and the methods to use to enforce this policy, see “The Life Cycle of a Cocoa Object”.



< Previous PageNext Page >


© 2006 Apple Computer, Inc. All Rights Reserved. (Last updated: 2006-12-20)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.