By this point we’ve covered the basics of the Objective-C language itself. With this knowledge and a willingness to learn, everything else can be picked up from Apple’s developer portal and the documentation. However, there are still topics which are used consistently through the language and are certainly worth talking about. This post's topic is the creation of objects in your code- proper practices and syntax.
Creating a new object in Objective-C is usually a two-step process. First, memory has to be allocated for the object, then the object is initialized with proper values.
The first step is accomplished through the alloc
class method, inherited from NSObject
and rarely, if ever, overridden. This method literally allocates a chunk of memory sufficient to hold your object (all of its instance variables). It then sets all of these values to zero or nil- this is so that you won’t get garbage values left behind by whatever had last used that chunk of memory. This means that all objects start out as nil, BOOL
s start as NO
, int
s as 0
and float
s are set to 0.0
. These values are guaranteed after the alloc method is called.
Fraction *frac = [Fraction alloc];
// numerator and denominator are both 0
However, that object is not quite ready to use yet- it has to be initialized first (otherwise strange bugs are mostly guaranteed). This is accomplished by the init instance method, which is often overridden. These methods return an object of type id
- the same object that is being initialized.
Fraction *frac = [[Fraction alloc] init];
It is important that you nest the alloc
and init
methods- there are instances where the initialization method may not return the same object as was allocated. This is true when working with classes such as NSString
, which are in fact a public interface for a variety of classes. In these cases, the initialization method may choose to return an object that is not quite the originally requested class (for example, NSString
‘s initialization methods might return a subclass of NSCFString.)
An initialization method for our Fraction class might look like this:
- (id)init {
if (!(self = [super init])) // Location 1
return nil; // Location 2
// Initialize fraction to 3/5 // Location 3
numerator = 3;
denominator = 5;
}
Let’s look at this code. At Location 1, we have… well, a source of controversy, actually. In general, the recommended practice is to do it as shown. Basically, this line states that if there is an issue with super’s init method and it returns nil, then your init method should also stop and return nil. Note that when a method hits a return statement, the method ends, even if there’s code left. This is why an else… block is not necessary. Why might [super init]
not work? One case is if the object was being initialized with data from the internet. If there isn’t an internet connection, or if the internet resource can’t be found, [super init]
might return nil, rather than some made-up or garbage data. At Location 2, we return nil, if necessary.
The method then assigns initial values to the instance variables at Location 3. In general, they should be set to reasonable, “empty” values if possible. Many programmers would (redundantly) set these values to zero, just to make their intentions clear. Here is a design decision you have to make. Assume you have a large class that has a lot of instance variables- a Car
, for example. In the init
method, you can choose to set default values for everything, if the intended use is to create a basic Car
and use it. However, if the user is expected to extensively customize the Car
, you might want to simply leave containers for the values, and not set default values.
Most classes also have convenience initializers, which take arguments with which to set the ivars.
- (id)initWithNumerator:(NSInteger)num overDenominator:(NSInteger)denom {
if (!(self = [super init]))
return nil;
numerator = num;
denominator = denom;
}
The code is quite self-explanatory. The method assigns the values in the arguments to the class’s instance variables.
In the case of multiple initializers, it is advised that one of them be the designated initializer -- generally it is the most complex initializer. This topic will be covered in next week’s Extension.
Languages such as Java have a concept of constructors, which are language-level constructs that are automatically invoked through the new keyword. They combine the process of allocation and initialization, although most of the custom code is for the latter. They also have syntax restrictions—the method must not return anything (that’- how it becomes a constructor), it must have the same name as the class, any call to super must be the first statement… Objective-C has none of these restrictions. As stated above, initializers are not part of the language itself—there is no keyword to invoke them. As such, they are simply regular methods, which usually end up calling NSObject’s init method. In Java, calling the superclass’s implementation must be the first line, to ensure that you’re not dealing with garbage data. There is no such restriction in Objective-C—but any issues with garbage data are the responsibility of the programmer, not the compiler. This is one of C’s design goals—give responsibility to the programmer. But with great power comes great responsibility!
This post is part of the Learn Objective-C in 24 Days course.