< Previous PageNext Page >

Object Creation

The creation of a Cocoa object always takes place in two stages: allocation and initialization. Without both steps an object generally isn’t usable. Although in almost all cases initialization immediately follows allocation, the two operations play distinct roles in the formation of an object.

In this section:

Allocating an Object
Initializing an Object
The dealloc Method
Class Factory Methods


Allocating an Object

When you allocate an object, part of what happens is what you might expect, given the term “allocate.” Cocoa allocates enough memory for the object from a region of application virtual memory. To calculate how much memory to allocate, it takes the object’s instance variables into account—including their types and order—as specified by the object’s class.

To allocate an object, you send the message alloc or allocWithZone: to the object’s class. In return, you get a “raw” (uninitialized) instance of the class. The alloc variant of the method uses the application’s default zone. A zone is a page-aligned area of memory for holding related objects and data allocated by an application. See Memory Management Programming Guide for Cocoa for more information on zones.

An allocation message does other important things besides allocating memory:

An object’s isa instance variable is inherited from NSObject, so it is common to all Cocoa objects. After allocation sets isa to the object’s class, the object is integrated into the runtime’s view of the inheritance hierarchy and the current network of objects (class and instance) that constitute a program. Consequently an object can find whatever information it needs at runtime, such as another object’s place in the inheritance hierarchy, the protocols that other objects conform to, and the location of the method implementations it can perform in response to messages.

In summary, allocation not only allocates memory for an object but initializes two small but very important attributes of any object: its isa instance variable and its retain count. It also sets all remaining instance variables to zero. But the resulting object is not yet usable. Initializing methods such as init must yet initialize objects with their particular characteristics and return a functional object.

Initializing an Object

Initialization sets the instance variables of an object to reasonable and useful initial values. It can also allocate and prepare other global resources needed by the object, loading them if necessary from an external source such as a file. Every object that declares instance variables should implement an initializing method—unless the default set-everything-to-zero initialization is sufficient. If an object does not implement an initializer, Cocoa invokes the initializer of the nearest ancestor instead.

The Form of Initializers

NSObject declares the init prototype for initializers; it is an instance method typed to return an object of type id. Overriding init is fine for subclasses that require no additional data to initialize their objects. But often initialization depends on external data to set an object to a reasonable initial state. For example, say you have an Account class; to initialize an Account object appropriately requires a unique account number, and this must be supplied to the initializer. Thus initializers can take one or more arguments; the only requirement is that the initializing method begins with the letters “init”. (The stylistic convention init... is sometimes used to refer to initializers.)

Note: Instead of an initializer with arguments, a subclass may implement only a simple init method and then use “set” accessor methods immediately after initialization to set the object to a useful initial state. (Accessor methods enforce encapsulation of object data by setting and getting the values of instance variables.)

Cocoa has plenty of examples of initializers with arguments. Here are a few (with the defining class in parentheses):

These initializers are instance methods that begin with “init” and return an object of the dynamic type id. Other than that, they follow the Cocoa conventions for multi-argument methods, often using WithType: or FromSource: before the first and most important argument.

Issues with Initializers

Although init... methods are required by their method signature to return an object, that object is not necessarily the one that was most recently allocated—the receiver of the init... message. In other words, the object you get back from an initializer might not be the one you thought was being initialized.

Two conditions prompt the return of something other than the just-allocated object. The first involves two related situations: when there must be a singleton instance or when the defining attribute of an object must be unique. Some Cocoa classes—NSWorkspace, for instance—allow only one instance in a program; a class in such a case must ensure (in an initializer or, more likely, in a class factory method) that only one instance is created, returning this instance if there is any further request for a new one. (See “Creating a Singleton Instance” for information on implementing a singleton object.)

A similar situation arises when an object is required to have an attribute that makes it unique. Recall the hypothetical Account class mentioned earlier. An account of any sort must have a unique identifier. If the initializer for this class—say, initWithAccountID:—is passed an identifier that has already been associated with an object, it must do two things:

By doing this, the initializer ensures the uniqueness of the identifier while providing what was asked for: an Account instance with the requested identifier.

Sometimes an init... method cannot perform the initialization requested. For example, an initFromFile: method expects to initialize an object from the contents of a file, the path to which is passed an argument. But if no file exists at that location the object cannot be initialized. A similar problem would happen if an initWithArray: initializer was passed an NSDictionary object instead of an NSArray object. When an init... method cannot initialize an object, it should:

Returning nil from an initializer indicates that the requested object cannot be created. When you create an object, you should generally check whether the returned value is nil before proceeding:

id anObject = [[MyClass alloc] init];
if (anObject) {
    [anObject doSomething];
    // more messages...
} else {
    // handle error
}

Because an init... method might return nil or an object other than the one explicitly allocated, it is dangerous to use the instance returned by alloc or allocWithZone: instead of the one returned by the initializer. Consider the following code:

id myObject = [MyClass alloc];
[myObject init];
[myObject doSomething];

The init method in this example could have returned nil or could have substituted a different object. Because you can send a message to nil without raising an exception, nothing would happen in the former case except (perhaps) a debugging headache. But you should always rely on the initialized instance instead of the “raw” just-allocated one. It is recommended that you nest the allocation and initialization messages and test the object returned from the initializer before proceeding.

id myObject = [[MyClass alloc] init];
if ( myObject ) {
    [myObject doSomething];
} else {
    // error recovery...
}

Once an object is initialized, you should not initialize it again. If you attempt a re-initialization, the framework class of the instantiated object often raises an exception. For example, the second initialization in this example would result in an NSInvalidArgumentException being raised.

NSString *aStr = [[NSString alloc] initWithString:@"Foo"];
aStr = [aStr initWithString:@"Bar"];

Implementing an Initializer

There are several critical steps to follow when implementing an init... method that serves as a class’s sole initializer or, if there are multiple initializers, its designated initializer (described in “Multiple Initializers and the Designated Initializer”):

The init... method in Listing 2-3 illustrates these steps:

Listing 2-3  An example of an initializer

- (id)initWithAccountID:(NSString *)identifier {
    if ( self = [super init] ) {
        Account *ac = [accountDictionary objectForKey:identifier];
        if (ac) { // object with that ID already exists
            [self release];
            return [ac retain];
        }
        if (identifier) {
            accountID = [identifier copy]; // accountID is instance variable
            [accountDictionary setObject:self forKey:identifier];
            return self;
        } else {
            [self release];
            return nil;
        }
    } else
        return nil;
}

Note: Although, for the sake of simplicity, this example returns nil if the argument is nil, the better Cocoa practice is to raise an exception.

It isn’t necessary to initialize all instance variables of an object explicitly, just those that are necessary to make the object functional. The default set-to-zero initialization performed on an instance variable during allocation is often sufficient. Make sure that you retain or copy instance variables, as required.

The requirement to invoke the superclass’ initializer as the first thing is important. Recall that an object encapsulates not only the instance variables defined by its class but the instance variables defined by all of its ancestor classes. By invoking super’s initializer first you help to ensure that the instance variables defined by classes up the inheritance chain are initialized first. The immediate superclass, in its initializer, invokes the initializer of its superclass, which invokes its superclass’s main init... method, and so on (see Figure 2-7). The proper order of initialization is critical because the later initializations of subclasses may depend on superclass-defined instance variables being initialized to reasonable values.


Figure 2-7  Initialization up the inheritance chain

Initialization up the inheritance chain

Inherited initializers are a concern when you create a subclass. Sometimes a superclass init... method sufficiently initializes instances of your class. But it is more likely it won’t, and so you should override it. If you don’t, the superclass implementation will be invoked, and since the superclass knows nothing about your class, your instances may not be correctly initialized.

Multiple Initializers and the Designated Initializer

A class can define more than one initializer. Sometimes multiple initializers let clients of the class provide the input for the same initialization in different forms. The NSSet class, for example, offers clients several initializers that accept the same data in different forms; one takes an NSArray object, another a counted list of elements, and another a nil-terminated list of elements:

- (id)initWithArray:(NSArray *)array;
- (id)initWithObjects:(id *)objects count:(unsigned)count;
- (id)initWithObjects:(id)firstObj, ...;

Some subclasses provide convenience initializers that supply default values to an initializer that takes the full complement of initialization parameters. This initializer is usually the designated initializer, the most important initializer of a class. For example, assume there is a Task class and it declares a designated initializer with this signature:

- (id)initWithTitle:(NSString *)aTitle date:(NSDate *)aDate;

The Task class might include secondary, or convenience, initializers that simply invoke the designated initializer, passing it default values for those parameters the secondary initializer doesn’t explicitly request (Listing 2-4).

Listing 2-4  Secondary initializers

- (id)initWithTitle:(NSString *)aTitle {
    return [self initWithTitle:title date:[NSDate date]];
}
 
- (id)init {
    return [self initWithTitle:@"Task"];
}

The designated initializer plays an important role for a class. It ensures that inherited instance variables are initialized by invoking super’s designated initializer. It is typically the init... method that has the most arguments and that does most of the initialization work, and it is the initializer that secondary initializers of the class invoke with messages to self.

When you define a subclass you must be able to identify the designated initializer of the superclass and invoke it in your subclass’s designated initializer through a message to super. You must also make sure that inherited initializers are covered in some way. And you may provide as many convenience initializers as you deem necessary. When designing the initializers of your class, keep in mind that designated initializers are chained to each other through messages to super while other initializers are chained to the designated initializer of their class through messages to self.

An example will make this clearer. Let’s say there are three classes, A, B, and C; class B inherits from class A, and class C inherits from class B. Each subclass adds an attribute as an instance variable and implements an init... method—the designated initializer—to initialize this instance variable. They also define secondary initializers and ensure that inherited initializers are overridden, if necessary. Figure 2-8 illustrates the initializers of all three class and their relationships.


Figure 2-8  Interactions of secondary and designated initializers

Interactions of secondary and designated initializers

The designated initializer for each class is the initializer with the most coverage; it is the method that initializes the attribute added by the subclass. The designated initializer is also the init... method that invokes the designated initializer of the superclass in a message to super. In this example, the designated initializer of class C, initWithTitle:date:, invokes the designated initializer of its superclass, initWithTitle:, which in turn invokes the init method of class A. When creating a subclass, it’s always important to know the designated initializer of the superclass.

While designated initializers are thus connected up the inheritance chain through messages to super, secondary initializers are connected to their class’s designated initializer through messages to self. Secondary initializers (as in this example) are frequently overridden versions of inherited initializers. Class C overrides initWithTitle: to invoke its designated initializer, passing it a default date. This designated initializer, in turn, invokes the designated initializer of class B, which is the overridden method, initWithTitle:. If you sent an initWithTitle: message to objects of class B and class C, you’d be invoking different method implementations. On the other hand, if class C did not override initWithTitle: and you sent the message to an instance of class C, the class B implementation would be invoked. Consequently, the C instance would be incompletely initialized (since it would lack a date). When creating a subclass, it’s important to make sure that all inherited initializers are adequately covered.

Sometimes the designated initializer of a superclass may be sufficient for the subclass, and so there is no need for the subclass to implement its own designated initializer. Other times, a class’s designated initializer may be an overridden version of its superclass’s designated initializer. This is frequently the case when the subclass needs to supplement the work performed by the superclass’s designated initializer, even though the subclass does not add any instance variables of its own (or the instance variables it does add don’t require explicit initialization).

The dealloc Method

In many respects, thedealloc method is the counterpart to a class’s init... method, especially its designated initializer. Instead of being invoked just after the allocation of an object, dealloc is invoked just prior to the object’s destruction. Instead of ensuring that the instance variables of an object are properly initialized, the dealloc method makes sure that object instance variables are released and that any dynamically allocated memory has been freed.

The final point of parallelism has to do with the invocation of the superclass implementation of the same method. In an initializer, you invoke the superclass’s designated initializer as the first step. In dealloc, you invoke the superclass’s dealloc implementation as the last step. The reason for this is mirror-opposite to that for initializers; subclasses should release or free the instance variables they own first before the instance variables of ancestor classes are released or freed.

Listing 2-5 shows how you might implement this method.

Listing 2-5  An example dealloc method

- (void)dealloc {
    [accountDictionary release];
    if ( mallocdChunk != NULL )
        free(mallocdChunk);
    [super dealloc];
}

Note that, as shown in this example, it’s prudent to check whether a malloc’d instance variable is non-NULL before attempting to free it. Because you can safely send a message to a nil object, such a precaution is not necessary with object instance variables.

Class Factory Methods

Class factory methods are implemented by a class as a convenience for clients. They combine allocation and initialization in one step and return the created object autoreleased. These methods are of the form + (type)className... (where className excludes any prefix).

Cocoa provides plenty of examples, especially among the “value” classes. NSDate includes the following class factory methods:

+ (id)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;

And NSData offers the follow factory methods:

+ (id)dataWithBytes:(const void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length
        freeWhenDone:(BOOL)b;
+ (id)dataWithContentsOfFile:(NSString *)path;
+ (id)dataWithContentsOfURL:(NSURL *)url;
+ (id)dataWithContentsOfMappedFile:(NSString *)path;

Factory methods can be more than a simple convenience. They can not only combine allocation and initialization, but the allocation can inform the initialization. As an example, let’s say you must initialize a collection object from a property-list file that encodes any number of elements for the collection (NSString objects, NSData objects, NSNumber objects, and so on). Before the factory method can know how much memory to allocate for the collection, it must read the file and parse the property list to determine how many elements there are what type each is.

Another purpose for a class factory method is to ensure that a certain class (NSWorkspace, for example) vends a singleton instance. Although an init... method could verify that only one instance exists at any one time in a program, it would require the prior allocation of a “raw” instance and then would have to release that instance. A factory method, on the other hand, gives you a way to avoid blindly allocating memory for an object that you might not use (see Listing 2-6).

Listing 2-6  A factory method for a singleton instance

static AccountManager *DefaultManager = nil;
 
+ (AccountManager *)defaultManager {
    if (!DefaultManager) DefaultManager = [[self allocWithZone:NULL] init];
    return DefaultManager;
}

Further Reading: For a more detailed discussion of issues relating to the allocation and initialization of Cocoa objects, see “The Objective-C Runtime System” in The Objective-C Programming Language.



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