Solution
Cocoa’s NSObject base class provides two methods that allocate memory for new instances, +(id)alloc and +(id)allocWithZone:(NSZone *)aZone. These methods are inherited by other Cocoa classes and are seldom overridden. The +alloc method is implemented to call the +allocWithZone: method specifying a default zone argument. Zones are briefly explained in the next section of this chapter. The +alloc and +allocWithZone: methods each return a pointer to a newly allocated block of memory large enough to store an instance of the class that executed the method. The allocated memory contains zeros except for the one instance variable, isa, that all Objective-C objects are required to have. The isa variable is automatically initialized to point to the class object that allocated the memory and is the tie-in to the Objective-C language runtime that enables the instance to receive messages such as -init that are used to complete initialization.
Zones
Memory zones are a feature of Cocoa intended to improve application performance by keeping the memory for objects that are used together close together in the computer’s address space. To explain how the location of objects in memory affects performance, it’s necessary to explain what happens when an application needs more memory than the amount of physical memory available.
Each Cocoa application has a very large amount of addressable memory. When an application dynamically allocates memory, the operating system provides memory even if all available physical memory in the computer is already being used. To accommodate the allocation request, the operating system copies the contents of some physical memory to the hard disk in an operation called paging or swapping. The physical memory that formerly contained the data written to disk is then made available to the application that needed more.
When memory that was copied to disk is needed again, the operating system copies a different area of physical memory to the disk and pages the old memory back into memory. The operating system is able to map the address space of each application to the physical memory even when memory is paged to the disk and back. This feature of the operating system is called virtual memory.
Using virtual memory affects performance because copying the contents of physical memory to and from the hard disk is time-consuming. Too much paging degrades system performance and is called thrashing. The location of memory allocated for object instances is important because if two or more objects that are used together are stored far apart in memory, the likelihood of thrashing increases.
Consider the following scenario: As the memory for one object is needed, it is paged into physical memory from the hard disk. That object then needs to access another object that is still not in physical memory, and even more memory needs be paged in. In the worst case, memory paged in for the second object forces memory for the first object to be paged out again. As the objects that reference each other interact, thrashing results.
Zones are used to make sure the memory allocated for objects that are used together is close together. When one of the objects is needed, the other is almost certainly also needed. Because the objects are in the same zone, the chances are good that all the needed objects are paged into memory at the same time, and when the objects are not needed, they are paged out together as well. Cocoa’s NSZone type specifies a C structure that identifies a memory zone. The +allocWithZone: method accepts an NSZone argument and allocates memory from the specified zone. Cocoa provides functions such as NSDefaultMallocZone(), NSCreateZone(), and NSRecycleZone() for managing memory zones. These functions are documented at /Developer/Documentation/Cocoa/ Reference/Foundation/ObjC_classic/Functions/FoundationFunctions.html and online at http://developer.apple.com/.
Initializing Allocated Memory
Once memory for a new instance is allocated, the memory is initialized by calling an instance method. Such instance methods are called initializers and by convention, begin with the word init, and return an id. Some of the advantages of using the id type are described in Chapter 7, “Anonymous Type and Heterogeneous Containers.” Allocation and initialization are almost always combined in one line of code with the following pattern: [[SomeClass alloc] init].
Classes can provide any number of initializers, and the different initializers can each accept different arguments. When multiple initializers are provided, one is usually the Designated Initializer. The Designated Initializer for the NSObject class is –(id)init, and the Designated Initializer for the NSView class is –(id)initWithFrame:(NSRect)aFrame. Any of the provided initializers can be the Designated Initializer, but it must be clearly documented. The Designated Initializer is usually the one that accepts the most arguments. All other initializers call the Designated Initializer in their implementations.
In addition to the Designated Initializer, most Cocoa classes provide an –(id)initWithCoder:(NSCoder *)aCoder method. The significance of -initWithCoder: is explained in Chapter 11, “Archiving and Unarchiving.”
Implementing the Designated Initializer
The Designated Initializer for each class must call the Designated Initializer of its superclass. The following simple MYCircle class is a subclass of NSObject:
@interface MYCircle : NSObject { NSPoint center; float radius; } // Designated Initializer - (id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius; @end @implementation MYCircle // Designated Initializer - (id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius { self = [super init]; if(nil != self) { center = aPoint; radius = aRadius; } return self; } @end
The -(id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius method first assigns the implicit self local variable to the result of calling the superclass’s Designated Initializer. This step is crucial because initializers sometimes return a different object than the one that received the message. This can happen when it is not possible to initialize the receiver correctly for some reason or when a pre-existing instance is returned to avoid the need to initialize a new one.
After the self variable is set, an if statement is used so that instance variables are only initialized if self isn’t nil. This is important because if self is nil, accessing the memory for the instance variables may be an error. This degree of defensive programming is usually unnecessary because few classes ever return nil from their initializers, but nil is a valid return value, so getting in the habit of checking for this case will prevent the occasional problem.
Finally, the -initWithCenter:radius: method returns self. This is the most common pattern for initializers.
Each class that introduces a new Designated Initializer must also override the inherited Designated Initializer to call the new one. Because the MYCircle class introduces -initWithCenter:radius:, it must also implement -init to call -initWithCenter:radius: as follows:
// Overriden inherited Designated Initializer - (id)init { static const float MYDefaultRadius = 1.0f; // call Designated Initializer with default arguments return [self initWithCenter:NSZeroPoint radius:MYDefaultRadius]; }
If you adhere to the following guidelines, calling any initializer implemented or inherited by a class will result in a correctly initialized instance:
- Make sure that the Designated Initializer calls its super class’ implementation of the super class’ Designated Initializer.
- Assign self to the object returned by the superclass’ Designated Initializer.
- Do not access instance variables if nil is returned by the superclass’ Designated Initializer.
- Make sure that the superclass’ Designated Initializer is overridden to call the new Designated Initializer.
- When subclassing, make sure every new initializer that isn’t the Designated Initializer calls the Designated Initializer.
These guidelines greatly simplify the task of creating subclasses. If the guidelines aren’t followed and some initializers fail to call the Designated Initializer, the only way a subclass can be implemented to assure correct initialization of instances is to override every inherited initializer.
Using Zones in Initializers
When using memory zones in your own code, it’s important to allocate memory used by instance variables from the same zone as the object that owns the instance variables. Storing references to memory outside of an object’s zone defeats the whole purpose of zones.
The zone used to allocate an object is determined by sending the -zone message to the object. The MYCircle class can be rewritten so that each instance stores an NSString label allocated from the same zone as the instance itself.
@interface MYCircle : NSObject { NSPoint center; float radius; NSString *label; } // Designated Initializer - (id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius; @end @implementation MYCircle // Designated Initializer - (id)initWithCenter:(NSPoint)aPoint radius:(float)aRadius { self = [super init]; if(nil != self) { center = aPoint; radius = aRadius; label = [[NSString allocFromZone:[self zone]] initWithString:@”default”]; } return self; } // Overriden inherited Designated Initializer - (id)init { // call Designated Initializer with default arguments return [self initWithCenter:NSZeroPoint radius:1.0f]; } @end
Objects aren’t the only things that can be allocated from zones. The NSZoneMalloc(), NSZoneCalloc(), and NSZoneFree() functions documented in /Developer/Documentation/ Cocoa/Reference/Foundation/ObjC_classic/Functions/FoundationFunctions.html are used to allocate and free blocks of arbitrary memory from specified zones. In Objective-C 2.0 introduced with Mac OS X 10.5, automatic garbage collection automatically frees memory that is allocated using void *__strong NSAllocateCollectable(NSUInteger size, NSUInteger options). For backward compatibility, calling NSAllocateCollectable() with the NSCollectorDisabledOption option has the same behavior as calling NSZoneMalloc().
Objects can also be copied and unarchived using specified zones. The –(id)copyWithZone:(NSZone *)aZone and –(id)mutableCopyWithZone::(NSZone *)aZone methods are explained in Chapter 12, “Copying.” The NSUnarchiver class provides the - (void)setObjectZone:(NSZone *)aZone method used to specify the zone in which unarchived objects are allocated. Archiving and Unarchiving are explained in Chapter 11.
Whenever objects are allocated, they must eventually be deallocated. Cocoa’s reference counted memory management system helps to ensure this is the case. It is described in Chapter 10, “Accessors,” which explains how to manage the memory used by objects. More information about Cocoa’s reference counted memory management is available at /Developer/Documentation/Cocoa/ObjectiveC/4objc_runtime_overview/ Object_Ownership.html.
In Mac OS X 10.5 and later, you can optionally use automatic garbage collection instead of reference counted memory management. However, because automatic garbage collection is optional in Cocoa, it’s necessary to correctly implement reference counted memory management in any new classes you create for the foreseeable future unless you require that the users of your class also use automatic garbage collection.
Regardless of whether zones are used, when an object is deallocated, its –(void)dealloc method is called. Don’t call -dealloc yourself. It’s called automatically when appropriate. The -dealloc method for MYCircle is implemented as follows to make sure the label instance variable allocated in the Designated Initializer is correctly handled:
- (void)dealloc { [label release]; [super dealloc]; }
If automatic garbage collection is used, the - (void)finalize method is automatically called instead of the –dealloc method. The MYCircle example doesn’t need to implement -finalize because the automatic garbage collector, if used, automatically collects the memory for the label string. MYCircle doesn’t require any special action when its memory is collected. However, it would be necessary to implement the –finalize method if MYCircle had allocated any noncollectible memory via NSAllocateCollectable() with the NSCollectorDisabledOption or needed to perform other end-of-life operations like closing open files.
Creating Temporary Instances
Many Cocoa classes provide methods that combine the two stages of allocating and initializing to return temporary instances. Such methods are called convenience methods. Convenience methods include the name of the class in the method’s name. For example, the NSString class provides the +(id)stringWithString:(NSString *)aString convenience method that’s similar to the -(id)initWithString:(NSString *)aString initializer method used by the MYCircle class. When not using automatic garbage collection, the primary difference between calling [[NSString alloc] initWithString:@”some string”] and [NSString stringWithString:@”some string”] is that +stringWithString: returns an instance that will be automatically deallocated unless you send it a -retain message to prevent deallocation. When using automatic garbage collection, there is no significant difference between the two techniques for obtaining a new instance.
Methods like +stringWithString: are usually implemented as follows:
+ (id)stringWithString:(NSString *)aString { return [[[self alloc] initWithString:aString] autorelease]; }
The implications of the -retain message and the -autorelease message are explained in Chapter 10.
The convenience methods for obtaining instances are almost always paired with similarly named initializers. Besides just reducing the amount of code programmers must write to create and initialize instances, the convenience methods also enable certain optimizations and are used with other patterns. In particular, convenience methods are used in the implementation of the Singleton and Class Clusters patterns in Chapter 13, “Singleton,” and Chapter 25, “Class Clusters,” respectively. Sometimes convenience methods return Flyweight objects, which are described in Chapter 22, “Flyweight” One drawback to using the convenience methods is that you give up flexibility in the way instances are allocated because the allocation technique is hard-coded in the method.