One of the reasons developers try to avoid learning Objective-C is in regard to its memory management (or rather, its lack of automation).
I'm going to cut right to the chase: in basic, single threaded applications, memory management really isn't all that awful, and with a few simple rules, you can avoid most of the common mistakes made.
Allocating & Deallocating Memory
When an object is created, space needs to be allocated to store it on the heap (an area of memory for objects). When that object is no longer in use, it should be deallocated from the heap in order to allow other objects to occupy that space. This act of deallocation runs two great risks:
- Forgetting to deallocate some memory before the pointer has dropped out of scope causes a "memory leak." This means that the memory space is still occupied, despite the fact that it is no longer able to be used (or, for that matter, released).
- Deallocating space before it's finished being used produces bugs which can be very difficult to track down. If you have multiple pointers which are referencing the same object, you obviously don't want one of the pointers to remove the object while others are still using it! Unfortunately, the moment an exception occurs could be light years away from when the deallocation took place (hence the difficulty in locating the source of the problem).
Retain & Release
Luckily, with Objective-C, allocating is easy (you simply call "alloc" on the class you wish to create an instance of):
1: Customer *cust = [[Customer alloc] init];
Allocating not only creates the object and a place for it in memory, it also adds a single "retain count." This count is used to determine whether or not an object should be deallocated. As long as an object has a positive "retain count," it will remain on the heap. Retain counts can be added explicitly by calling "retain" on a pointer.
[Detailed rules on when you should add a retain count are discussed below.]
This "retain count" means that you will never have to deallocate an object yourself. Instead you "release" an object that you've allocated (or became "an owner of" - more on that below), when you are finished.
Every time a "release" message is received, the retain count is decremented. The memory, however, will not be deallocated until the retain count has reached zero. This helps reduce errors that would occur by accidentally deallocating an object while it is still "on active duty."
The Golden Rule: Symmetry
If you only remember one thing from this article, let it be this: If you are an owner of an object you MUST release the object when you have finished. Likewise, if you are not an owner of an object, you MUST NOT release the object. Therefore what you are looking for is symmetry in your code. Retain -> Release. That's it. All of the other rules and use cases derive from this simple concept. The next natural questions are:
- What does it mean to be an owner?
- When do I want to become an owner?
- How do I become an owner?
What Does It Mean to be an Owner?
You're the owner of an object when your code has added a retain count to the referenced object. This retain count could have been added directly (by calling "retain" on the pointer):
... or indirectly by calling a "creation" function - more on that below.
When Do I Want to Become an Owner?
You are an owner of any object you create, whether you want to be, or not (again, more on that below). In addition to that scenario, you'll generally want to become an owner when you are keeping the object beyond the scope of a single function (as an instance variable). If you aren't an owner of an instance variables that points to an object, you run the risk that the object will be deallocated before you are finished using it. Therefore, it's important to make sure you add a retain count for all instances variables that point to objects (unless you already added one implicitly, by calling a "creation" function). There are other exceptions & less common situations that you may run into as well, but this is the big one to remember.
How Do I Become an Owner?
As stated above, you can become an owner of an object by calling the retain function.
You also become an owner by calling a "creation" function. This is a function that not only returns a pointer to an object, it also adds a retain count on your behalf.
You can recognize a "creation" function by its name: it will start with, or simply be, one of these words: "Alloc," "New," "Copy," or "mutableCopy." Since a "creation" function adds a retain count on your behalf, you are implicitly the owner of that object, and must eventually call "release."
It is generally assumed that if these words do not appear in the name of the function, and you haven't called "retain" on the pointer, you are not an owner of the object. Therefore you should NOT release the object when you are finished using it.
1: NSString *myStr1 = [[NSString alloc] initWithString:@"Intertech" ];
// An owner of the NSString (alloc) -- you must eventually call "release."
2: NSString *myStr2 = [[NSString stringWithString:@"Intertech"];
// NOT an owner of the NSString. -- you should NOT call "release."
Autorelease
When a function has a return value of a pointer to an object that it creates, the retain count needs to be handled in a special way. For example, look at the following code... What's wrong with it?
1: -(Customer *) customer
2: {
3: Customer *cust = [[Customer alloc] init];
4: return cust;
5: }
We've broken the golden rule! We've created an object with "alloc" (therefore adding a retain count), but we haven't released it before the "cust" pointer has fallen out of scope! So how about this example... any better?
1: -(Customer *) customer
2: {
3: Customer *cust = [[Customer alloc] init];
4: [cust release];
5: return cust;
6: }
Well... now we have symmetry (retain -> release), however it's possible that the Customer object will be deallocated before the caller is able to use and/or retain it!
To get around this problem, Objective-C gives us a special kind of release called "Autorelease." This function provides a deferred release, meaning we have given up ownership, but the object won't be released until a later time. It does this by adding the release to an "autorelease pool." You'll notice that this pool is automatically created and "drained" in your main() function by Xcode:
1: int main (int argc, const char * argv[])
2: {
3: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
4:
5: // ...
6:
7: [pool drain];
8: return 0;
9: }
Therefore, you typically don't need to manage the pool yourself. Instead, simply call "autorelease" when you have created an object in a function (unless your function is a "creation" function, which means the name should start with the one of the "creation" names: "Alloc," "New," "Copy," or "mutableCopy." In that case, the caller would expect you to leave the retain count on their behalf).
1: -(Customer *) customer
2: {
3: Customer *cust = [[Customer alloc] init];
4: return [cust autorelease];
5: }
The Dealloc Function
When your object has been released enough times that the "retain count" has hit zero, the OS will deallocate your object from memory. However, most objects that you create will be composed of pointers to other objects. Consider the following interface:
1: @interface Customer : NSObject {}
2: @property (nonatomic, retain) FullName *custName;
3: @property (nonatomic, retain) Address *custAddr;
4: @end
When the customer is deallocated, what happens to the FullName and Address objects? We wouldn't want to have the OS blindly deallocate these two objects, in case they are still in use by other blocks of code. Instead, we should be given the opportunity to remove our retain count on these properties (if appropriate) before the Customer object is deallocated. This is done through a special function called "dealloc." It is your responsibility to create this function and release all retained instance variables (a call to the parent dealloc should be made at the very end of this function):
1: -(void) dealloc
2: {
3: [custName release];
4: [custAddr release];
5: [super dealloc];
6: }
Putting it together
Here is an example that contains several memory management problems. See if you can find them all:
1: #import "FullName.h"
2: @interface Customer : NSObject {
3: FullName *_custName;
4: }
5: - (Customer *) customer;
6: - (FullName *) custName;
7: - (void) setCustName: (FullName *) newValue;
8:
9: @end
10:
11: @implementation Customer
12:
13: - (Customer *) customer
14: {
15: Customer *cust = [[Customer alloc] init];
16: return cust;
17: }
18:
19: - (FullName *) custName
20: {
21: [_custName retain];
22: return _custName;
23: }
24:
25: - (void) setCustName: (FullName *) newValue
26: {
27: _custName = newValue;
28: }
29:
30: - (void) dealloc
31: {
32: [super dealloc];
33: }
34:
35: @end
Problem #1: The Customer function on lines 13 - 17 has broken symmetry. It creates an object with "alloc" but does not "autorelease" it. Remember, when a function creates and returns an object, it should call autorelease, rather than release (for details, review the "Autorelease" section above).
Corrected:
1: - (Customer *) customer
2: {
3: Customer *cust = [[Customer alloc] init];
4: return [cust autorelease];
5: }
Problem #2: The custName function on lines 19 - 23 adds a retain count, but doesn't release it. In fact, there is no real reason to add a retain count here, since we haven't created a method that uses one of the four "creation" names ("add," "new," "copy," and "mutableCopy").
Corrected:
1: - (FullName *) custName
2: {
3: return _custName;
4: }
Problem #3: The setCustName: function on lines 25 - 28 are storing an instance variable, but are not retaining the reference. Without a retain, it is possible the object will be deallocated before we are finished using it. In this case, we need to release the old value and retain the new value. Don't worry about whether or not there is already an old value set for this instance variable. Unlike other languages (such as the NullPointerException in Java), Objective-C will quietly ignore the "release" call if nothing is there.
Corrected:
1: - (void) setCustName: (FullName *) newValue
2: {
3: [_custName release];
4: _custName = [newValue retain];
5: }
Problem #4: We forgot to release our instance variable in the dealloc method!
Corrected:
1: - (void) dealloc
2: {
3: [_custName release];
4: [super dealloc];
5: }
P.S. - Use Your Tools!
While following the rules described above will drastically reduce the amount of memory issues you experience in your code, you'll probably still miss a few due to exceptional situations and common mistakes. Thankfully, the iOS SDK includes several tools to help you identify the over and under releasing of objects. In Xcode you can use the "Analyze" build option to see if it detects any memory errors. Also, Instruments provides a "Leaks" template (identifies under releasing) and a "Zombies" template (identifies over releasing). I'll explore these tools in a future post!