< Previous PageNext Page > Hide TOC

Allocating and Initializing Objects

In this section:

Allocating and Initializing Objects
The Returned Object
Implementing an Initializer
The Designated Initializer
Combining Allocation and Initialization


Allocating and Initializing Objects

It takes two steps to create an object using Objective-C. You must:

An object isn’t fully functional until both steps have been completed. Each step is accomplished by a separate method but typically in a single line of code:

id anObject = [[Rectangle alloc] init];

Separating allocation from initialization gives you individual control over each step so that each can be modified independently of the other. The following sections look first at allocation and then at initialization, and discuss how they are controlled and modified.

In Objective-C, memory for new objects is allocated using class methods defined in the NSObject class. NSObject defines two principal methods for this purpose, alloc and allocWithZone:.

These methods allocate enough memory to hold all the instance variables for an object belonging to the receiving class. They don’t need to be overridden and modified in subclasses.

The alloc and allocWithZone: methods initialize a newly allocated object’s isa instance variable so that it points to the object’s class (the class object). All other instance variables are set to 0. Usually, an object needs to be more specifically initialized before it can be safely used.

This initialization is the responsibility of class-specific instance methods that, by convention, begin with the abbreviation “init”. If the method takes no arguments, the method name is just those four letters, init. If it takes arguments, labels for the arguments follow the “init” prefix. For example, an NSView object can be initialized with an initWithFrame: method.

Every class that declares instance variables must provide an init... method to initialize them. The NSObject class declares the isa variable and defines an init method. However, since isa is initialized when memory for an object is allocated, all NSObject’s init method does is return self. NSObject declares the method mainly to establish the naming convention described earlier.

The Returned Object

An init... method normally initializes the instance variables of the receiver, then returns it. It’s the responsibility of the method to return an object that can be used without error.

However, in some cases, this responsibility can mean returning a different object than the receiver. For example, if a class keeps a list of named objects, it might provide an initWithName: method to initialize new instances. If there can be no more than one object per name, initWithName: might refuse to assign the same name to two objects. When asked to assign a new instance a name that’s already being used by another object, it might free the newly allocated instance and return the other object—thus ensuring the uniqueness of the name while at the same time providing what was asked for, an instance with the requested name.

In a few cases, it might be impossible for an init... method to do what it’s asked to do. For example, an initFromFile: method might get the data it needs from a file passed as an argument. If the file name it’s passed doesn’t correspond to an actual file, it won’t be able to complete the initialization. In such a case, the init... method could free the receiver and return nil, indicating that the requested object can’t be created.

Because an init... method might return an object other than the newly allocated receiver, or even return nil, it’s important that programs use the value returned by the initialization method, not just that returned by alloc or allocWithZone:. The following code is very dangerous, since it ignores the return of init.

id anObject = [SomeClass alloc];
[anObject init];
[anObject someOtherMessage];

Instead, to safely initialize an object, you should combine allocation and initialization messages in one line of code.

id anObject = [[SomeClass alloc] init];
[anObject someOtherMessage];

If there’s a chance that the init... method might return nil (see “Handling Initialization Failure”), then you should check the return value before proceeding:

id anObject = [[SomeClass alloc] init];
if ( anObject )
    [anObject someOtherMessage];
else
    ...

Implementing an Initializer

When a new object is created, all bits of memory (except for isa)—and hence the values for all its instance variables—are set to 0. In some situations, this may be all you require when an object is initialized; in many others, you want to provide other default values for an object’s instance variables, or you want to pass values as arguments to the initializer. In these other cases, you need to write a custom initializer. In Objective-C, custom initializers are subject to more constraints and conventions than are most other methods.

Constraints and Conventions

There are several constraints and conventions that apply to initializer methods that do not apply to other methods:

The following example illustrates the implementation of a custom initializer for a class that inherits from NSObject and has an instance variable, creationDate, that represents the time when the object was created:

- (id)init {
    // Assign self to value returned by super's designated initializer
    // Designated initializer for NSObject is init
    if (self = [super init]) {
        creationDate = [[NSDate alloc] init];
    }
    return self;
}

(The reason for using the if (self = [super init]) pattern is discussed in “Handling Initialization Failure.”)

An initializer doesn’t need to provide an argument for each variable. For example, if a class requires its instances to have a name and a data source, it might provide an initWithName:fromURL: method, but set nonessential instance variables to arbitrary values or allow them to have the null values set by default. It could then rely on methods like setEnabled:, setFriend:, and setDimensions: to modify default values after the initialization phase had been completed.

The next example illustrates the implementation of a custom initializer that takes a single argument. In this case, the class inherits from NSView. It shows that you can do work before invoking the super class’s designated initializer.

- (id)initWithImage:(NSImage *)anImage {
 
    // Find the size for the new instance from the image
    NSSize size = anImage.size;
    NSRect frame = NSMakeRect(0.0, 0.0, size.width, size.height);
 
    // Assign self to value returned by super's designated initializer
    // Designated initializer for NSView is initWithFrame:
    if (self = [super initWithFrame:frame]) {
 
        image = [anImage retain];
    }
    return self;
}

This example doesn’t show what to do if there are any problems during initialization; this is discussed in the next section.

Handling Initialization Failure

In general, if there is a problem during an initialization method, you should call [self release] and return nil.

There are two main consequences of this policy:

Note: You should only call [self release] at the point of failure. If you get nil back from an invocation of the superclass’s initializer, you should not also call release. You should simply clean up any references you set up that are not dealt with in dealloc and return nil. This is typically handled by the pattern of performing initialization within a block dependent on a test of the return value of the superclass’s initializer—as seen in previous examples:

- (id)init {
    if (self = [super init]) {
        creationDate = [[NSDate alloc] init];
    }
    return self;
}

The following example builds on that shown in “Constraints and Conventions” to show how to handle an inappropriate value passed as the parameter:

- (id)initWithImage:(NSImage *)anImage {
 
    if (anImage == nil) {
        [self release];
        return nil;
    }
 
    // Find the size for the new instance from the image
    NSSize size = anImage.size;
    NSRect frame = NSMakeRect(0.0, 0.0, size.width, size.height);
 
    // Assign self to value returned by super's designated initializer
    // Designated initializer for NSView is initWithFrame:
    if (self = [super initWithFrame:frame]) {
 
        image = [anImage retain];
    }
    return self;
}

The next example illustrates best practice where, in the case of a problem, there is a possibility of returning meaningful information in the form of an NSError object returned by reference:

- (id)initWithURL:(NSURL *)aURL (NSError **)errorPtr {
 
    if (self = [super init]) {
 
        NSData *data = [[NSData alloc] initWithContentsOfURL:aURL
                                       options:NSUncachedRead error:errorPtr];
 
        if (data == nil) {
            // In this case the error object is created in the NSData initializer
            [self release];
            return nil;
        }
        // implementation continues...

You should typically not use exceptions to signify errors of this sort—for more information, see Error Handling Programming Guide For Cocoa.

Coordinating Classes

The init... methods a class defines typically initializes only those variables declared in that class. Inherited instance variables are initialized by sending a message to super to perform an initialization method defined somewhere farther up the inheritance hierarchy:

- (id)initWithName:(NSString *)string {
    if ( self = [super init] ) {
        name = [string copy];
    }
    return self;
}

The message to super chains together initialization methods in all inherited classes. Because it comes first, it ensures that superclass variables are initialized before those declared in subclasses. For example, a Rectangle object must be initialized as an NSObject, a Graphic, and a Shape before it’s initialized as a Rectangle.

The connection between the initWithName: method illustrated above and the inherited init method it incorporates is illustrated in Figure 3-1:


Figure 3-1  Incorporating an Inherited Initialization Method


A class must also make sure that all inherited initialization methods work. For example, if class A defines an init method and its subclass B defines an initWithName: method, as shown in Figure 3-1, B must also make sure that an init message successfully initializes B instances. The easiest way to do that is to replace the inherited init method with a version that invokes initWithName::

- init {
    return [self initWithName:"default"];
}

The initWithName: method would, in turn, invoke the inherited method, as shown earlier. Figure 3-2 includes B’s version of init:


Figure 3-2  Covering an Inherited Initialization Model


Covering inherited initialization methods makes the class you define more portable to other applications. If you leave an inherited method uncovered, someone else may use it to produce incorrectly initialized instances of your class.

The Designated Initializer

In the example given in “Coordinating Classes,” initWithName: would be the designated initializer for its class (class B). The designated initializer is the method in each class that guarantees inherited instance variables are initialized (by sending a message to super to perform an inherited method). It’s also the method that does most of the work, and the one that other initialization methods in the same class invoke. It’s a Cocoa convention that the designated initializer is always the method that allows the most freedom to determine the character of a new instance (usually this is the one with the most arguments, but not always).

It’s important to know the designated initializer when defining a subclass. For example, suppose we define class C, a subclass of B, and implement an initWithName:fromFile: method. In addition to this method, we have to make sure that the inherited init and initWithName: methods also work for instances of C. This can be done just by covering B’s initWithName: with a version that invokes initWithName:fromFile:.

- initWithName:(char *)string {
    return [self initWithName:string fromFile:NULL];
}

For an instance of the C class, the inherited init method invokes this new version of initWithName: which invokes initWithName:fromFile:. The relationship between these methods is shown in Figure 3-3:


Figure 3-3  Covering the Designated Initializer


This figure omits an important detail. The initWithName:fromFile: method, being the designated initializer for the C class, sends a message to super to invoke an inherited initialization method. But which of B’s methods should it invoke, init or initWithName:? It can’t invoke init, for two reasons:

Therefore, initWithName:fromFile: must invoke initWithName::

- initWithName:(char *)string fromFile:(char *)pathname {
    if ( self = [super initWithName:string] )
        ...
}

General Principle: The designated initializer in a class must, through a message to super, invoke the designated initializer in a superclass.

Designated initializers are chained to each other through messages to super, while other initialization methods are chained to designated initializers through messages to self.

Figure 3-4 shows how all the initialization methods in classes A, B, and C are linked. Messages to self are shown on the left and messages to super are shown on the right.


Figure 3-4  Initialization Chain


Note that B’s version of init sends a message to self to invoke the initWithName: method. Therefore, when the receiver is an instance of the B class, it invokes B’s version of initWithName:, and when the receiver is an instance of the C class, it invokes C’s version.

Combining Allocation and Initialization

In Cocoa, some classes define creation methods that combine the two steps of allocating and initializing to return new, initialized instances of the class. These methods are often referred to as convenience constructors and typically take the form + className... where className is the name of the class. For example, NSString has the following methods (among others):

+ (id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc;
+ (id)stringWithFormat:(NSString *)format, ...;

Similarly, NSArray defines the following class methods that combine allocation and initialization:

+ (id)array;
+ (id)arrayWithObject:(id)anObject;
+ (id)arrayWithObjects:(id)firstObj, ...;

Important: It is important to understand the memory management implications of using these methods if you do not use garbage collection (see “Memory Management”). You must read Memory Management Programming Guide for Cocoa to understand the policy that applies to these convenience constructors.

Notice that the return type of these methods is id. This is for the same reason as for initializer methods, as discussed in “Constraints and Conventions.”

Methods that combine allocation and initialization are particularly valuable if the allocation must somehow be informed by the initialization. For example, if the data for the initialization is taken from a file, and the file might contain enough data to initialize more than one object, it would be impossible to know how many objects to allocate until the file is opened. In this case, you might implement a listFromFile: method that takes the name of the file as an argument. It would open the file, see how many objects to allocate, and create a List object large enough to hold all the new objects. It would then allocate and initialize the objects from data in the file, put them in the List, and finally return the List.

It also makes sense to combine allocation and initialization in a single method if you want to avoid the step of blindly allocating memory for a new object that you might not use. As mentioned in “The Returned Object,” an init... method might sometimes substitute another object for the receiver. For example, when initWithName: is passed a name that’s already taken, it might free the receiver and in its place return the object that was previously assigned the name. This means, of course, that an object is allocated and freed immediately without ever being used.

If the code that determines whether the receiver should be initialized is placed inside the method that does the allocation instead of inside init..., you can avoid the step of allocating a new instance when one isn’t needed.

In the following example, the soloist method ensures that there’s no more than one instance of the Soloist class. It allocates and initializes a single shared instance:

+ (Soloist *)soloist {
    static Soloist *instance = nil;
 
    if ( instance == nil ) {
        instance = [[self alloc] init];
    }
    return instance;
}

Notice that in this case the return type is Soloist *. Since this method returns a singleton share instance, strong typing is appropriate—there is no expectation that this method will be overridden.



< Previous PageNext Page > Hide TOC


© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)


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.