Instance Variable Memory Management Policies
In the preceding section, we saw that an instance variable might be set by retaining or copying the incoming value. There are in fact three possible memory-management policies for an instance variable:
retain
The incoming value is retained (and the existing value is released).
copy
The incoming value is copied (and the existing value is released). This policy is typical where the class of the instance variable is an immutable class with a mutable subclass (such as NSString, NSArray, or NSDictionary), in case we are handed a mutable instance that might be subsequently mutated by some other object.
assign
The incoming value is directly assigned to the instance variable; it is not retained, nor is the existing value released. This policy is followed when it would be wrong
for us to assert ownership of the incoming object. For example, an object would not normally retain its delegate.
The “assign” policy must also be used to prevent a retain cycle, a situation in which two objects retain each other. This must never be allowed to happen, because both objects will leak (neither can have its retain count reach zero by normal means). For example, in a system of orders and items, an order needs to know what its items are and an item might need to know what orders it is a part of, but it must not be the case both that an order retains its items and that an item retains its orders.
Note that you must not release, in dealloc, an instance variable whose policy is “assign.”You would be releasing something you never retained or generated by copying, which is a no-no (and a likely crasher).
Autorelease
Here’s how autorelease works. Your code runs in the presence of something called an autorelease pool. (If you look in main.m, you can actually see such one such pool being created.) When you send autorelease to an object, that object is placed in the autorelease pool, and a number is incremented saying how many times this object has been placed in this autorelease pool. From time to time, when nothing else is going on, the autorelease pool is automatically destroyed and replaced by another. At the moment when an autorelease pool is destroyed, it sends release to each of its objects, the same number of times as that object was placed in this autorelease pool. This is called draining the pool. If that causes an object’s retain count to be zero, fine; the object is destroyed in the usual way. So autorelease is just like release — effectively, it is a form of release — but with a proviso, “later, not right this second.”
Sometimes you may wish to drain the autorelease pool immediately. Consider the following:
for (NSString* aWord in myArray) {
NSString* lowerAndShorter = [[aWord lowercaseString] substringFromIndex:1];
[myMutableArray addObject: lowerAndShorter];
}
Every time through that loop, two objects are added to the autorelease pool: the low-ercase version of the string we start with, and the shortened version of that. The first object, the lowercase version of the string, is purely an intermediate object: as the current iteration of the loop ends, no one except the autorelease pool has a pointer to it. If this loop had very many repetitions, or if these intermediate objects were themselves very large in size, this could add up to a lot of memory. These intermediate objects will all be released when the autorelease pool drains, so they are not leaking; nevertheless, they are accumulating in memory, and in certain cases there could be a danger that we will run out of memory before the autorelease pool drains. The problem can be even more acute than you know, because you might repeatedly call a built-in Cocoa method that itself accumulates a lot of intermediate objects.
When you release an autorelease pool, it is drained, so the objects it contains are sent release then and there:
for (NSString* aWord in myArray) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSString* lowerAndShorter = [[aWord lowercaseString] substringFromIndex:1];
[myMutableArray addObject: lowerAndShorter];
[pool release];
}
Nib Loading and Memory Management
On iOS, when a nib loads, the top-level nib objects that it instantiates are autoreleased. So if someone doesn’t retain them, they’ll eventually vanish in a puff of smoke. There are two primary strategies for preventing that from happening:
Outlet graph with retain
A memory management graph is formed: every top-level object is retained by another top-level object (without retain cycles, of course), with the File’s Owner as the start of the graph. So, the File’s Owner proxy has an outlet to a top-level object, and this outlet is backed by an accessor whose setter uses a retain policy. Thus, when the nib loads, the nib owner retains this top-level object (and must, of course, remember to release it before it itself goes out of existence). And so on, for every top-level object. This is the strategy you’ll typically use when loading a nib.
Mass retain
The call to loadNibNamed:owner:options: returns an NSArray of the nib-instantiated objects; retain this NSArray. This is the strategy used by UIApplicationMain when it loads the app’s main nib.
Objects in the nib that are not top-level objects are already part of a memory management object graph, so there’s no need for you to retain them directly. For example, if you have a top-level UIView in the nib, and it contains a UIButton, the UIButton is the UIView’s subview — and a view retains its subviews and takes ownership of them. Thus, it is sufficient to manage the UIView’s memory and to let the UIView manage the UIButton.
However, if you have an outlet to this UIButton, you must be extremely careful! Recall that this outlet will be linked to its source instance at nib-loading time using KVC. This means that if you declare an ivar as an IBOutlet, with no corresponding setter, KVC will add an extra retain to the target instance as it sets the ivar directly, which can cause a memory leak. The best practice, therefore, is always to supply an accessor. An accessor with an assign policy will defend against the entire problem.
Mac OS X Programmer Alert
Memory management for nib-loaded instances is different on iOS than on Mac OS X. On Mac OS X, nib-loaded instances are not autoreleased, so they don’t have to be retained, and memory management is usually automatic in any case because the file’s owner is usually an NSWindowController, which takes care of these things for you. On iOS, memory management of top-level nib objects is up to you. On Mac OS X, an outlet to a non-top-level object does not cause an extra retain if there is no accessor for the corresponding ivar; on iOS, it does.