Simple Memory Management
Memory management comes down to two simple rules. At creation, every object has a retain count of 0. At release, every object has (or, more accurately, is about to have) a retain count of 0. It is up to you as a developer to manage an object’s retention over its lifetime. You should ensure that it moves from start to finish without being prematurely released and guarantee that it does finally get released when it is time to do so. Complicating matters is Objective-C’s autorelease pool. If some objects are autoreleased and others must be released manually, how do you best control your objects? Here’s a quick-and-dirty guide to getting your memory management right.
Creating Objects
Any time you create an object using the alloc/init pattern, you build it with a retain count of 1. It doesn’t matter which class you use or what object you build, alloc/init produces a +1 count.
id myObject = [[SomeClass alloc] init];
For locally scoped variables, if you do not release the object before the end of a method, the object leaks. Your reference to that memory goes away, but the memory itself remains allocated. The retain count remains at +1.
- (void) leakyMethod { // This is leaky NSArray *array = [[NSArray alloc] init]; }
The proper way to use an alloc/init pattern is to create, use, and then release. Releasing brings the retain count down to 0. When the method ends, the object is deallocated.
- (void) properMethod { NSArray *array = [[NSArray alloc] init]; // use the array here [array release]; }
Autorelease objects do not require an explicit release statement for locally scoped variables. (In fact, avoid doing so to prevent double-free errors that will crash your program.) Sending the autorelease message to an object marks it for autorelease. When the autorelease pool drains at the end of each event loop, it sends release to all the objects it owns.
- (void) anotherProperMethod { NSArray *array = [[[NSArray alloc] init] autorelease]; // This won't crash the way release would printf("Retain count is %d\n", [array retainCount]); // use the array here }
By convention, object-creation class methods return an autoreleased object. The NSArray class method array returns a newly initialized array that is already set for autorelease. The object can be used throughout the method, and its release is handled when the autorelease pool drains.
- (void) yetAnotherProperMethod { NSArray *array = [NSArray array]; // use the array here }
At the end of this method, the autoreleased array can return to the general memory pool.
Creating Autoreleased Objects
As a rule, whenever you ask another method to create an object, it’s good programming practice to return that object autoreleased. Doing so consistently lets you follow a simple rule: “If I didn’t allocate it, then it was built and returned to me as an autorelease object.”
- (Car *) fetchACar { Car *myCar = [[Car alloc] init]; return [myCar autorelease]; }
This holds especially true for class methods. By convention, all class methods that create new objects return autorelease objects. These are generally referred to as “convenience methods.” Any object that you yourself allocate is not set as autorelease unless you specify it yourself.
// This is not autoreleased Car *car1 = [[Car alloc] init]; // This is autoreleased Car *car2 = [[[Car alloc] init] autorelease]; // By convention, this *should* be an autoreleased object Car *car3 = [Car car];
To create a convenience method at the class level, make sure to define the class with the + prefix (instead of -) and to return the object after sending autorelease to it:
+ (Car *) car { return [[[Car alloc] init] autorelease]; }
Autorelease Object Lifetime
So how long can you use an autorelease object? What guarantees do you have? The hard-and-fast rule is that the object is yours until the next item in the event loop gets processed. The event loop is triggered by user touches, by button presses, by “time passed” events, and so forth. In human reckoning, these times are impossibly short; in the iOS SDK device’s processor frame of reference, they’re quite large. As a more general rule, you can assume that an autoreleased object should persist throughout the duration of your method call.
Once you return from a method, guarantees go out the window. When you need to use an array beyond the scope of a single method or for extended periods of time (for example, you might start a custom run-loop within a method, prolonging how long that method endures), the rules change. You must retain autorelease objects to increase their count and prevent them from getting deallocated when the pool drains; when the autorelease pool calls release on their memory, they’ll maintain a count of at least +1.
Never rely on an object’s retainCount to keep track of how often it has already been retained. If you want to make absolutely sure you own an object, retain it, use it, and then release it when you’re done. If you’re looking at anything other than your own object’s relative retain counts and matching releases, you’re going to run into systemic development errors.
Retaining Autorelease Objects
You can send retain to autorelease objects just like any other object. Retaining objects set to autorelease allows them to persist beyond a single method. Once retained, an autorelease object is just as subject to memory leaks as one you created using alloc/init. For example, retaining an object that’s scoped to a local variable might leak, as shown here:
- (void)anotherLeakyMethod { // After returning, you lose the local reference to // array and cannot release. NSArray *array = [NSArray array]; [array retain]; }
Upon creation, array has a retain count of +1. Sending retain to the object brings that retain count up to +2. When the method ends and the autorelease pool drains, the object receives a single release message; the count returns to +1. From there, the object is stuck. It cannot be deallocated with a +1 count, and with no reference left to point to the object, it cannot be sent the final release message it needs to finish its life cycle. This is why it’s critical to build references to retained objects.
By creating a reference, you can both use a retained object through its lifetime and be able to release it when you’re done. Set references via an instance variable (preferred) or a static variable defined within your class implementation. If you want to keep things simple and reliable, use retained properties built from those instance variables. The next section shows you how retained properties work and demonstrates why they provide the solution of choice for developers.
Retained Properties
Retained properties hold onto data that you assign to them and properly relinquish that data when you set a new value. Because of this, they tie in seamlessly to basic memory management. Here’s how you create and use retained properties in your iOS applications.
First, declare your retained property in the class interface by including the retain keyword between parentheses:
@property (retain) NSArray *colors;
Then synthesize the property methods in your implementation:
@synthesize colors;
When given the @synthesize directive, Objective-C automatically builds routines that manage the retained property. The routines automatically retain an object when you assign it to the property. That behavior holds regardless of whether the object is set as autorelease. When you reassign the property, the previous value is automatically released.
Assigning Values to Retained Properties
When working with retained properties, you need to be aware of two patterns of assignment. These patterns depend on whether or not you’re assigning an autorelease object. For autorelease-style objects, use a simple single assignment. This assignment sets the colors property to the new array and retains it:
myCar.colors = [NSArray arrayWithObjects: @"Black", @"Silver", @"Gray", nil];
The array is created and returned as an autorelease object with a count of +1. The assignment to the retained colors property brings the count to +2. Once the current event loop ends, the autorelease pool sends release to the array, and the count drops back to +1.
For normal (non-autorelease) objects, release the object after assigning it. Upon creation, the retain count for a normally allocated object is +1. Assigning the object to a retained property increases that count to +2. Releasing the object returns the count to +1.
// Non-autorelease object. Retain count is +1 at creation NSArray *array = [[NSArray alloc] initWithObjects:@"Black", @"Silver", @"Gray", nil]; // Count rises to +2 via assignment to a retained property myCar.colors = array; // Now release to get that retain count back to +1 [array release];
You often see this pattern of create, assign, release in iOS development. You might use it when assigning a newly allocated view to a view controller object. Here’s an example:
UIView *mainView = [[UIView alloc] initWithFrame:aFrame]; self.view = mainView; [mainView release];
These three steps move the object’s retain count from +1 to +2 and then back to +1.
A final count of +1 guarantees you that can use an object indefinitely. At the same time, you’re assured that the object deallocates properly when the property is set to a new value and release is called on its prior value. That release on a +1 object allows the object to deallocate.
Reassigning a Retained Property
When you’re done using a retained property, regardless of the approach used to create that object, set the property to nil or to another object. This sends a release message to the previously assigned object.
myCar.colors=nil;
If the colors property had been set to an array, as just shown, that array would automatically be sent a release message. Since each pattern of assignment produced a +1 retained object, this reassignment would send release to the +1 object. The object’s life would be over.
Avoiding Assignment Pitfalls
Within a class implementation, it’s handy to use properties to take advantage of this memory management behavior. To take advantage of this, avoid using instance variables directly. Direct assignment like this won’t retain the array or release any previous value. This is a common pitfall for new iOS developers. Remember the dot notation when accessing the instance variables.
colors = [NSArray arrayWithObjects: @"Black", @"Silver", @"Gray", nil];
This same caution holds true for properties defined as assign. Note the following behavior carefully. Although both
@property NSArray *colors;
and
@property (assign) NSArray *colors;
allow you to use dot notation, assignment via these properties does not retain or release objects. Assign properties expose the colors instance variable to the outside world, but they do not provide the same memory management that retain properties do.
High Retain Counts
Retain counts that go and stay above +1 do not necessarily mean you’ve done anything wrong. Consider the following code segment. It creates a view and starts adding it to arrays. This raises the retain count from +1 up to +4.
// On creation, view has a retain count of +1; UIView *view = [[[UIView alloc] init] autorelease]; printf("Count: %d\n", [view retainCount]); // Adding it to an array increases that retain count to +2 NSArray *array1 = [NSArray arrayWithObject:view]; printf("Count: %d\n", [view retainCount]); // Another array, retain count goes to +3 NSArray *array2 = [NSArray arrayWithObject:view]; printf("Count: %d\n", [view retainCount]); // And another +4 NSArray *array3 = [NSArray arrayWithObject:view]; printf("Count: %d\n", [view retainCount]);
Notice that each array was created using a class convenience method and returns an autoreleased object. The view is set as autorelease, too. Some collection classes such as NSArray automatically retain objects when you add them into an array and release them when either the object is removed (mutable objects only) or when the collection is released. This code has no leaks because every one of the four objects is set to properly release itself and its children when the autorelease pool drains.
When release is sent to the three arrays, each one releases the view, bringing the count down from +4 to +1. The final release, when the object is at +1, allows the view to deallocate when this method finishes: no leaks, no further retains, no problems.
Other Ways to Create Objects
You’ve seen how to use alloc to allocate memory. Objective-C offers other ways to build new objects. You can discover these by browsing class documentation as the methods vary by class and framework. As a rule of thumb, if you build an object using any method whose name includes alloc, new, create, or copy, you maintain responsibility for releasing the object. Unlike class convenience methods, methods that include these words generally do not return autoreleased objects.
Sending a copy message to an object, for example, duplicates it. copy returns an object with a retain count of +1 and no assignment to the autorelease pool. Use copy when you want to duplicate and make changes to an object while preserving the original. Note that for the most part, Objective-C produces shallow copies of collections like arrays and dictionaries. It copies the structure of the collection, and maintains the addresses for each pointer, but does not perform a deep copy of the items stored within.
C-Style Object Allocations
As a superset of C, Objective-C programs for the iOS SDK often use APIs with C-style object creation and management. Core Foundation (CF) is a Cocoa Touch framework with C-based function calls. When working with CF objects in Objective-C, you build objects with CFAllocators and often use the CFRelease() function to release object memory.
There are, however, no simple rules. As the following code shows, you may end up using free(), CFRelease(), and custom methods such as CGContextRelease() all in the same scope, side-by-side with standard Objective-C class convenience methods such as imageWithCGImage:. The function used to create the context object used here is CGBitmapContextCreate(), and like most Core Foundation function calls, it does not return an autoreleased object. This code snippet builds a UIImage, the iOS SDK class that stores image data.
UIImage *buildImage(int imgsize) { // Create context with allocated bits CGContextRef context = MyCreateBitmapContext(imgsize, imgsize); CGImageRef myRef = CGBitmapContextCreateImage(context); free(CGBitmapContextGetData(context)); // Standard C free() CGContextRelease(context); // Core Graphics Release UIImage *img = [UIImage imageWithCGImage:myRef]; CFRelease(myRef); // Core Foundation Release return img; }
Carbon and Core Foundation
Working with Core Foundation comes up often enough that you should be aware of its existence and be prepared to encounter its constructs, specifically in regard to its frameworks. Frameworks are libraries of classes you can utilize in your application.
Table 3-2 explains the key terms involved. To summarize the issue, early OS X used a C-based framework called Core Foundation to provide a transitional system for developing applications that could run on both Classic Mac systems as well as Mac OS X. Although Core Foundation uses object-oriented extensions to C, its functions and constructs are all C based, not Objective-C based.
Table 3-2. Key OS X Development Terms
Term |
Definition |
Foundation |
The core classes for Objective-C programming, offering all the fundamental data types and services needed for Cocoa and Cocoa Touch. A section at the end of this chapter intro-duces some of the most important Foundation classes you'll use in your applications. |
Core Foundation |
A library of C-based classes that are based on Foundation APIs but that are implemented in C. Core Foundation uses object-oriented data but is not built using the Objective-C classes. |
Carbon |
An early set of libraries provided by Apple that use a proce-dural API. Carbon offered event handling support, a graphics library, and many more frameworks. Some Carbon APIs live on through Core Foundation. Carbon was introduced for the Classic Mac OS, first appearing in Mac OS 8.1. |
Cocoa |
Apple's collection of frameworks, APIs, and runtimes that make up the modern Mac OS X runtime system. Frameworks are primarily written in Objective-C, although some continue to use C/C++. |
Cocoa Touch |
Cocoa's equivalent for the iOS SDK, where the frameworks are tuned for the touch-based mobile iOS user experience. Some iOS frameworks such as Core Audio and Open GL are considered to reside outside Cocoa Touch. |
Toll Free Bridging |
A method of Cocoa/Carbon integration. Toll Free Bridging refers to sets of interchangeable data types. For example, Cocoa's Foundation (NSString *) object can be used interchangeably with Carbon's Core Foundation's CFStringRef. Bridging connects the C-based Core Foun-dation with the Objective-C Foundation world. |
Core Foundation technology lives on through Cocoa. You can and will encounter C-style Core Foundation when programming iOS applications using Objective-C. The specifics of Core Foundation programming fall outside the scope of this chapter, however, and are best explored separately from learning how to program in Objective-C.
Deallocating Objects
iPhone devices use reference count–managed Objective-C. On the iPhone, iPod touch, and iPad, there’s no garbage collection and little likelihood there ever will be. Every object cleans up after itself. So what does that mean in practical terms? Here’s a quick rundown of how you end an object’s life, cleaning up its instance variables and preparing it for deallocation.
Instance variables must release retained objects before deallocation. You as the developer must ensure that those objects return to a retain count of 0 before the parent object is itself released. To do this, you implement dealloc, a method automatically called by the runtime system when an object is about to be released. If you use a class with object instance variables (that is, not just floats, ints, and Bools), you probably need to implement a deallocation method. The basic dealloc method structure looks like this:
- (void) dealloc { // Class-based clean-up clean up my own instance variables here // Clean up superclass [super dealloc] }
The method you write should work in two stages. First, clean up any instance variables from your class. Then ask your superclass to perform its cleanup routine. The special super keyword refers to the superclass of the object that is running the dealloc method. How you clean up depends on whether your instance variables are automatically retained.
You’ve read about creating objects, building references to those objects, and ensuring that the objects’ retain counts stay at +1 after creation. Now, you see the final step of the object’s lifetime, namely releasing those +1 objects so they can be deallocated.
Retained Properties
In the case of retained properties, set those properties to nil using dot notation assignment. This calls the custom setter method synthesized by Objective-C and releases any prior object the property has been set to. Assuming that prior object had a retain count of +1, this release allows that object to deallocate:
self.make = nil;
Variables
When using plain (non-property) instance variables or assign-style properties, send release at deallocation time. Say, for example, you’ve defined an instance variable called salesman. It might be set at any time during the lifetime of your object. The assignment of salesman might look like this:
// release any previous value [salesman release]; // make the new assignment. Retain count is +1 salesman = [[SomeClass alloc] init];
This assignment style means that salesman could point to an object with a +1 retain count at any time during the object’s lifetime. Therefore, in your dealloc method, you must release any object currently assigned to salesman. You can guard this with a check if salesman is not nil, but practically, you’re free to send release to nil without consequence, so feel free to skip the check.
[salesman release];
A Sample Deallocation Method
Keeping with an expanded Car class that uses retained properties for make, model, and colors, and that has a simple instance variable for salesman, the final deallocation method would look like this. The integer year and the Boolean forSale instance variables are not objects and do not need to be managed this way.
- (void) dealloc { self.make = nil; self.model = nil; self.colors = nil; [salesman release]; [super dealloc]; }
Managing an object’s retain count proves key to making Objective-C memory management work. Few objects should continue to have a retain count greater than +1 after their creation and assignment. By guaranteeing a limit, your final releases in dealloc are ensured to produce the memory deallocation you desire.
Cleaning Up Other Matters
The dealloc method offers a perfect place to clean up shop. For example, you might need to dispose of an Audio Toolbox sound or perform other maintenance tasks before the class is released. These tasks almost always relate to Core Foundation, Core Graphics, Core Audio, or similar C-style frameworks.
if (snd) AudioServicesDisposeSystemSoundID(snd);
Think of dealloc as your last chance to tidy up loose ends before your object goes away forever. Whether this involves shutting down open sockets, closing file pointers, or releasing resources, use this method to make sure your code returns state as close to pristine as possible.