Memory Management Part I

Not too long ago I came under the painful realization that I had been hacking my way through iOS apps without fully understanding how Memory Management works in Objective-C. Up to this point my memory management tactic was waiting for the compiler to complain and then take the suggestion the compiler provided. If that failed, Stack Overflow to the rescue! While this usually fixed the problem and works fine on small applications, it probably isn’t the best tactic when working with apps of a larger scope where performance is a big issue. So hopefully with this article I can help clarify things for you so you’re not left stuttering stupidly in an interview when they ask “What’s the difference between the stack and heap?” or “How does reference counting work?”.

Assumptions

  • You have a fairly good grasp on basics of iOS using ARC. That is, you may not fully understand it but you can make properties and classes and such.

The Heap

I like to think of the heap as a big file cabinet in memory where objects are stored. Whenever you create an object using alloc, copy, or new, several drawers of the file cabinet are reserved for the data inside the object. What data you ask? well first there’s the isa pointer, which lets the Objective-c runtime know what KIND of object it’s dealing with. Then you have the instance variables; These would be primitives such as int or char types. Finally you have pointers to other objects.

Say we have a new object, like a ninja object. Ninjas might have a name, skill level, and a bunch of throwing stars. So the interface might look like Code Chunk 1

Code Chunk 1: An example of a Ninja object


@interface Ninja : NSObject
{

    NSString *ninjaName;
    int ninjaSkillLevel;
    int ninjaStarQuantity;

}

@end

Now going back to our filing cabinet analogy. When the Ninja object gets created it would take up four drawers: one for the isa pointer, one for the ninja name pointer, one for the ninja skill level int, and one for the ninja star quanity int. The ninja name pointer, points to another set of drawers on the heap which represents an NSString object. This is so the size of the ninja object doesn’t change if the size of the ninja name object changes. The int types have a set maximum size so they won’t grow and are allocated inside the ninja object. See Figure 1 if you’re a visual person. This is sort of how memory looks, except in memory the drawers could change size.

Figure_1

Figure 1: A memory allocation analogy

Where to properties go?

Properties are just instance variables where you’re getter and setter methods (accessor methods) are conveniently created for you. So in terms of memory, It’s going to look the same as an a pointer to an object on the heap. We’ll talk more about properties in part 2 of this article.

The Stack

Unlike the heap, where objects are thrown in whatever group of drawers is available. The stack would be more like putting files on top of the filing cabinet. Each file would contain a set of instructions and the values needed to carry out the instructions. When one file calls on another file’s instructions. That file gets added to the stack. When the instructions in the top file are completed it gets taken off the stack along with the values inside the file. This process repeats until there are no more files on the stack.

To say it in non-analogous terms. Every method (function) called from each previous method gets added to the stack. Each method can contain values, or instance variables. Each call is referred to as a frame. The stack continuously grows until no more method calls happen. The stack will shrink as methods complete their instructions. When methods are popped off the stack or deallocated, the local variables inside those methods also get deallocated.

For a more visual example. What if we made our ninja initiate a fight? In the main class we would call the “initiateNinjaFight” method which will call two more methods in the Ninja class. Those methods will call one method each. Figure 2 shows what that might look like on the stack. This is a rather large image, so you may need to open it up in a new window to see what is going on, but you can clearly see that everything happens in a last in first out (LIFO) order.

Figure_2

Figure 2: Stack frames over time

Memory Management and Object Ownership

At the heart of iOS memory management is object ownership. An object or method is said to own another object if it maintains a pointer to it. For a method, that can be a local variable that points to another object. For for an object, that can be an instance variable that points to an object.

In iOS you use a memory management scheme called refference counting to determine when objects get destroyed. When objects are destroyed the memory that is allocated to them gets freed up. If an entity claims ownership of an object the reference count for that object is increased by 1. When new objects are created they start with a reference count of 1 because whatever entity created the object is now an owner. Now if another object claims ownership of the object, then the refference count would go to 2. If all ownership of the object is relenquished then the reference count for that object goes to zero and consequently the object gets destroyed.

Let’s turn to an example to hopefully solidify this concept. Code Chunk 2 shows a few lines of code in the viewDidLoad method of a single view controller application. This code chunks shows how you would manually manage the reference counting. In Xcode you can manually manage your memory by disabling ARC or Automatic Refference Counting found under the build settings.

You can see by this example a new object always results in a reference count of 1. Then we add the object to the mutable array which takes ownership and causes the reference count to go to 2. Now we can release newItem once without it being destroyed because the array has ownership. If we release the array, the array object also resigns ownership and the reference count goes to 0. If you try to send a message to newItem you will get a EXC_BAD_ACCESS error. This error basically means you are trying to message an object that is not there or has been destroyed due to the 0 reference count.

Code Chunk 2: A simple reference counting example


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSMutableArray *array = [[NSMutableArray alloc] init];

    NSString *string = [[NSString alloc] initWithFormat:@"A String"];    // +1
    [array addObject:string];                                            // +1
    [string release];                                                    // -1
    [array release];                                                     // -1
                                                                         //=====
                                                                         //  0

    NSLog(@"%@",string); //This line will throw a EXC_BAD_ACCESS  error since "string" is now deallocated

}

With manual reference counting we can increase the reference count by using “retain”. Code Chunk 3 shows an example of this. Now you won’t get the EXC_BAD_ACCESS error, but this also results in a memory leak. When manually managing memory you need to alway balance the objects you create with either a relase or autorelease. More specifically if you ever use alloc, copy, or retain you must balance them with either release or autorelease.

Code Chunk 3: A Simple reference counting example showing a retain and a memory leak


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSMutableArray *array = [[NSMutableArray alloc] init];

    NSString *string = [[NSString alloc] initWithFormat:@"A String"];    // +1
    [array addObject:string];                                            // +1
    [string retain];                                                     // +1
    [string release];                                                    // -1
    [array release];                                                     // -1
                                                                         //=====
    NSLog(@"%@",string);                                                 // +1

}

Code Chunk 4 shows an example of how you might choose to use autorelease. In this example we autorelease the string when we create it. This will defer the count down of the reference count until the viewDidLoad method ends and the autorelease pool in the main thread executes. You’ll see here that we also fixed the memory leak issue as our net releases equal the number of claimed ownerships.

Code Chunk 4: A Simple reference counting example showing the use of autorelease


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSMutableArray *array = [[NSMutableArray alloc] init];

    NSString *string = [[[NSString alloc] initWithFormat:@"A String"] autorelease];    // +1 & -1 later
    [array addObject:string];                                                          // +1
    [string retain];                                                                   // +1
    [string release];                                                                  // -1
    [array release];                                                                   // -1
                                                                                       //=====
    NSLog(@"%@",string);                                                               //  0

Note: When trying to find leaks in your application you can use the analyze tool in xcode to quickly root out the majority of them. Simply click and hold on the play button in Xcode and choose “analyze” from the drop down that will eventually pop up. This will analyze the application and look for potential problems.

Double Note: The use of autorelase here might not be the best example, but it illustrates what it does.

To summarize here. You need to balance all instances of alloc, retain, and copy with release and autorelease when doing manual reference counting. There are of course a bunch of gotchas when troubleshooting reference counting errors that can quickly become a headache. Fortunately, Apple gave use ARC which eliminates a ton of this work. In Part two of this article we’ll talk a bit about ARC and go into depth a bit more about properties.

About Joe Hoffman

I am An Electrical Engineer that spends a lot of my off time doing web development and other programming. Currently I am trying to expand my knowledge with iOS and I find that writing tutorials helps me learn more thoroughly as well as provide some useful info to others.

Comments

  1. Justin Clarke says:

    Thank Joe!

    The ninja example really helped! I never really grasped the concept but I do now!

Speak Your Mind

*

css.php
Privacy Policy