Allocating and Initializing Objects
The Returned Object
Implementing an Initializer
The Designated Initializer
Combining Allocation and Initialization
It takes two steps to create an object using Objective-C. You must:
Dynamically allocate memory for the new object
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.
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 |
... |
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.
There are several constraints and conventions that apply to initializer methods that do not apply to other methods:
By convention, the name of a custom initializer method begins with init
.
Examples from the Foundation framework include, initWithFormat:
, initWithObjects:
, and initWithObjectsAndKeys:
.
The return type of an initializer method should be id
.
The reason for this is that id
gives an indication that the class is purposefully not considered—that the class is unspecified and subject to change, depending on context of invocation. For example, NSString
provides a method initWithFormat:
. When sent to an instance of NSMutableString
(a subclass of NSString
), however, the message returns an instance of NSMutableString
, not NSString
. (See also, though, the singleton example given in “Combining Allocation and Initialization.”)
In the implementation of a custom initializer, you must untimately invoke a designated initializer.
Designated initializers are described in “The Designated Initializer”; a full explanation of this issue is given in “Coordinating Classes.”
In brief, if you are implementing a new designated initializer, it must invoke the superclass’ designated initialiser. If you are implementing any other initializer, it should invoke its own class’s designated initializer, or another of its own initializers that ultimately invokes the designated initializer.
By default (such as with NSObject
), the designated initializer is init
.
You should assign self
to the value returned by the initializer.
This is because the initializer could return a different object than the original receiver.
If you set the value of an instance variable, you typically do so using direct assignment rather than using an accessor method.
This avoids the possibility of triggering unwanted side-effects in the accessors.
At the end of the initializer, you must return self
, unless the initializer fails in which case you return nil
.
Failed initializers are discussed in more detail in “Handling Initialization Failure.”
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.
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:
Any object (whether your own class, a subclass, or an external caller) that receives a nil
from an initializer method should be able to deal with it. In the unlikely case where the caller has established any external references to the object before the call, this includes undoing any connections.
You must make sure that dealloc
methods are safe in presence of partially-initialized objects.
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.
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:
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
:
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.
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:
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:
Circularity would result (init
invokes C’s initWithName:
, which invokes initWithName:fromFile:
, which invokes init
again).
It won’t be able to take advantage of the initialization code in B’s version of initWithName:
.
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.
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.
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.
© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)