Thursday 13 October 2011

Memory Management in Objective-C Tutorial



Can you manage memory and eat sushi at the same time?
Can you manage memory and eat sushi at the same time?
When I review code from other developers, it seems that the most common mistakes are centered around memory management in Objective-C. If you are used to a language that handles memory management for you like Java or C#, things can be quite confusing!
So in this tutorial, you’ll learn how memory management works in Objective-C by getting some hands-on experience. We’ll talk about how reference counting works, and go through each of the memory-management keywords by building a real-world example – an app about your favorite types of sushi!
This tutorial is intended for beginner iOS developers, or intermediate developers in need of a refresher on this topic. Advanced developers might be more interested in some of theother tutorials on this site.
So without further ado, let’s get coding!

Getting Started

In XCode, go to File\New Project, choose iOS\Application\Navigation-based Application, and name the new project PropMemFun. Go to Build\Build and Run, and you should see an empty table view in your simulator like this:
Blank Screen from Navigation-Based XCode Template
Let’s say we wanted to fill this list in with our favorite types of sushi. The easiest way to do this is to make an array holding the string names of each type of sushi, and then each time we display a row, put the appropriate string from the array into the table view cell.
So start by declaring an instance variable for the sushi types array in RootViewController.h, as follows:
#import <UIKit/UIKit.h>
 
@interface RootViewController : UITableViewController {
    NSArray * _sushiTypes;
}
 
@end
By declaring this, each instance of RootViewController will have space to store a pointer to an NSArray, which is the Objective-C class you use for arrays that don’t change after initialization. If you need to change the array after initialization (for example, add an item into it later on), you should use NSMutableArray instead.
You may be wondering why we named the variable with an underscore in front. This is just something I personally like to do that I think makes things easier. I’ll talk more about why I like do this when I write a follow-up tutorial about Objective-C properties. But for now note that all we’ve done so far is add an instance variable – we haven’t done anything with properties yet – and we’ve named it with an underscore just as a personal preference, it doesn’t do anything special.
Now, go to RootViewController.m, uncomment viewDidLoad, and set it up like the following:
- (void)viewDidLoad {
    [super viewDidLoad];
 
    _sushiTypes = [[NSArray alloc] initWithObjects:@"California Roll", 
                   @"Tuna Roll", @"Salmon Roll", @"Unagi Roll", 
                   @"Philadelphia Roll", @"Rainbow Roll",
                   @"Vegetable Roll", @"Spider Roll", 
                   @"Shrimp Tempura Roll", @"Cucumber Roll",
                   @"Yellowtail Roll", @"Spicy Tuna Roll",
                   @"Avocado Roll", @"Scallop Roll",
                   nil];
}
Now we’re getting into memory management! Objects you create in Objective-C are reference-counted. This means that each object keeps track of how many other objects have a pointer to it. Once the reference count goes down to zero, the memory for the object can be safely released.
So your job, as a programmer, is to make sure that the reference count is always accurate. When you store a pointer to an object somewhere (like in an instance variable), you need to increment the reference count, and when you’re done with it you need to decrement the reference count.
“But OMG”, you may be thinking, “that sounds really complicated and confusing!” Don’t worry, it’s much easier to do than it sounds – let’s see with some hands-on!

Init the Sapporo, Release Your Inhibitions

Whenever you create an object in Objective-C, you first call alloc on the object to allocate space for it, then you call an init method to initialize the object. When the init method doesn’t take any parameters, sometimes you’ll see programmers take a shortcut by calling new instead (which is the same as alloc followed by init).
But the important thing is once this is all done, you’re returned a new object with the retain count set to 1. So when you’re done with it, you need to decrement the reference count.
Ok, so let’s give that a shot. Still in RootViewController.m, go to the end of your file, and set up your viewDidUnload and dealloc methods like the following:
- (void)viewDidUnload {
    [_sushiTypes release];
    _sushiTypes = nil;
}
 
- (void)dealloc {
    [_sushiTypes release];
    _sushiTypes = nil;
    [super dealloc];
}
Remember that when you created the array with alloc/init, it had a retain count of 1. So when you’re done with the array, you need to decrement the retain count. In Objective-C, you can do this by calling releaseon the object.
But where should you release it? Well, you should definitely release the array in dealloc, because obviously when this object is deallocated it will no longer need the array. Also, whenever you create an object in viewDidLoad (setting the reference count to 1), you should release the object in viewDidUnload. Don’t worry too much about this for now – some day I might write a separate tutorial on that subject.
Note that you also set the object to nil afterwards. This is a good practice, because by setting it to nil it avoids a lot of problems. Any time you call a method on a nil object, it does nothing, but if you don’t set it to nil, if you tried calling a method on a deallocated object your program should crash.
Ok, now let’s make use of our new array. First replace the “return 0;” in tableView:numberOfRowsInSection with the following:
// Replace "return 0;" in tableView:numberOfRowsInSection with this
return _sushiTypes.count;
This says that the number of rows in the table should be equal to the number of rows in the sushiTypes array.
Now we need to tell the table view what to display for each row, so add the following tableView:cellForRowAtIndexPath, underneath where it says “Configure the cell”:
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString = 
    [[NSString alloc] initWithFormat:@"%d: %@", 
        indexPath.row, sushiName]; // 2
cell.textLabel.text = sushiString; // 3
[sushiString release]; // 4
Let’s go through this line by line:
  1. Looks up the string in the sushiTypes array corresponding to the current row.
  2. We want to display a string such as “3: Unagi Roll”, where “3″ is the row number and “Unagi Roll” is the name of the sushi for that row. An easy way to construct a string out of separate pieces like this is to use the initWithFormat initializer on NSString, so we call that here. Remember that after this is complete, the retain count of the returned string will be 1.
  3. Set the text for the current row to be the formatted string. The text label will make a copy of the string when it’s set.
  4. We’re done with the sushiString, so we release it. If you forget to call this, you’ll have a memory leak, because the string will have a retain count of 1 that is never decremented.
Compile and run your code, and if all works well, you should see the list of sushi in the table.
TableView listing Sushi

Autorelease Your Potential

So far, you know that when you call alloc/init, the retain count is 1, and when you’re done with an object you need to call release to get the reference count to 0.
Next let’s talk about another important method you can call on an object – autorelease.
When you autorelease an object, it’s kind of like saying “I want you to be released at some point in the future, like the next time the run loop gets called. But for now I want to be able to use it.”
The easiest way to see it is in action. Modify tableView:cellForRowAtIndexPath underneath where it says “Configure the cell” to look like the following:
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString = 
    [[[NSString alloc] initWithFormat:@"%d: %@", 
        indexPath.row, sushiName] autorelease]; // 2
cell.textLabel.text = sushiString; // 3
So, just two changes from last time. First you added a call to autorelease at the end of line 2. Second, you removed the release at the end.
Here’s how this works. After line 2, sushiString has a retain count of 1, but it has one autorelease pending. This means you can continue to use sushiString for the rest of this method, but the next time the run loop is called, the memory will go away.
In this case, that’s great for us because we want to use the sushiString right now, but don’t need it later on. However, if we tried to store the variable somewhere (without retaining it) and use it later on (such as when a button is tapped), we’d have a big problem, because we’d be trying to access released memory and the app might crash.
Sometimes when you call methods, they return objects to you that have a retain count of 1, but have an autorelease pending. You can see what I mean by modifying tableView:cellForRowAtIndexPath underneath where it says “Configure the cell” once more to look like the following:
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString = 
    [NSString stringWithFormat:@"%d: %@", 
        indexPath.row, sushiName]; // 2
cell.textLabel.text = sushiString; // 3
The change here is just in line 2. Instead of calling alloc/init/autorelease, you now call a helper method on NSString called stringWithFormat. This method returns a new NSString with a retain count of 1, but has an autorelease pending. So just like the last example, it’s OK to use the string here, but you’d better not save it and try to use it again somewhere else (without retaining it) or the app might crash.
You might wonder how you can if a method returns an object with an autorelease pending or not. Well, there’s a simple convention that can tell you the answer to that:
  • If the method name begins with init or copy, the object returned will have a retain count of 1, and no autorelease pending. In other words, you own that object and have to release it when you’re done.
  • If the method name begins with anything else, the object returned will have a retain count of 1, and an autorelease pending. In other words, you can use the object right now, but if you want to use it later you have to retain the object.

Retain Your Wits

What if you have an object that has an autorelease pending that you want to use later on? You just need to call retain on it! That will bump the reference count up to 2, and later on when the autorelease fires it will go down to 1, and your object won’t be deallocated (since the reference count is still positive).
Let’s see how this works. Open up RootViewController.h and add another instance variable inside the @interface:
NSString * _lastSushiSelected;
This just makes a new instance variable, which will be used to keep track of the string corresponding to the last row selected.
Next modify tableView:didSelectRowAtIndexPath as follows:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
    NSString * sushiString = [NSString stringWithFormat:@"%d: %@", 
        indexPath.row, sushiName]; // 2
 
    NSString * message = [NSString stringWithFormat:@"Last sushi: %@.  Cur sushi: %@", _lastSushiSelected, sushiString]; // 3
    UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:@"Sushi Power!" 
                                                         message:message 
                                                        delegate:nil 
                                               cancelButtonTitle:nil 
                                               otherButtonTitles:@"OK", nil] autorelease]; // 4
    [alertView show]; // 5
 
    [_lastSushiSelected release]; // 6
    _lastSushiSelected = [sushiString retain]; // 7
 
}
There’s a lot here, so let’s go over things line by line.
  1. Looks up the string in the sushiTypes array corresponding to the current row.
  2. Constructs a string representing the current row. Notice it uses the stringWithFormat method, which returns a new string. Since the method name does not begin with init or copy, you know that the retain count is 1, with a pending autorelease. Remember, that means you can use the returned string now, but if you want to use it later you have to retain the string.
  3. Constructs a message to display the last sushi selected and the current sushi selected. Just like the above, the stringWithFormat method returns an autorelease object, which works well for this case because you want to use it now, but don’t need it anymore after you display the popup.
  4. Creates an alert view to display the popup message. This is created with alloc/init, and then autorelease is called on the object so that it will be deallocated automatically later on.
  5. Shows the alert view.
  6. Before you can set the lastSushiSelected instance variable, you need to release the current lastSushiSelected object. If lastSushiSelected is nil, it’s OK, this will just do nothing.
  7. You want to be able to use the lastSushiSelected string later, so you need to retain it. Before you call retain, lastSushiSelected has a retain count of 1, and a pending autorelease. Afterwards, it has a retain count of 2, and a pending autorelease. When the autorelease takes effect, the retain count goes down to 1, and since the retain count is positive, it isn’t released yet, so it’s still safe to use the object later on.
There’s one more thing you have to add. To make sure there are no memory leaks, you need to release lastSushiSelected when dealloc is called on RootViewController. So add the following to your dealloc method:
[_lastSushiSelected release];
_lastSushiSelected = nil;
Basically, when your dealloc method is called, you need to release any objects you have referenced.
Compile and run your code, and now when you select a row, you should display the current row string and the last row string (which has been retained!)
Sushi Info in Alert View

Reference Count The Material

Let’s review what we’ve covered so far.
  • When you call alloc/init, you are returned an object with a retain count of 1.
  • So when you’re done with the object, you need to call release to decrement the reference count.
  • When you call a method that begins with anything except init or copy, you’re returned an object that will be autoreleased at some point in the future.
  • So if you want to use that object later, you need to call retain to increment the reference count.
  • If you create an object with alloc/init and want it to automatically be released at some point in the future for you, you can call autorelease on the object.
This should give you a handle of the basics of memory management in Objective-C. For more information, take a look at Apple’s Memory Management Programming Guide.

Where To Go From Here?

Here is a sample project we developed in the above tutorial.
No matter how good of a developer you are and how well you understand memory management, you’re bound to make some mistakes and leak memory at some point. So my next tutorial will cover how to debug memory leaks using XCode, Instruments, and Zombies. So get your ammunition ready!
I’m also planning a tutorial at some point about Objective-C properties and how they relate to memory management, another area that I think is a common point of confusion for beginners. Stay tuned!

0 comments:

Post a Comment

 

Copyright @ 2013 PakTechClub.