Getting Objective, part 4: Please release me (let me go)


22 January 2012, by

previous article in series

Recap

Last time, we enhanced our Hello World application so that it could interpret our feedback and take some appropriate action. I closed off by declaring it to be “very bad indeed“. This time, we’re going to find out why…

Remembering Memory

Those of you who’ve done a Computer Science course will be familiar with the concept of Memory Management. At least, I assume that you will be – I studied Psychology…

Any computer has a certain amount of memory available to it, in some combination of RAM and permanent storage. On your iPhone or iPad, the permanent storage is used for storing your music, apps, contacts, location, etc., while the RAM is accessed by the operating system and by your apps to temporarily store anything directly relevant to whatever you’re doing at the time. For the rest of this post, when I refer to “memory”, you can assume that I will mean the RAM.

Any variable that we use has to occupy some space in the memory. For example:

// This is simple and safe
- (void) aSimpleFunction {
    NSString *myString = @"This string is in memory now";
    // We can do anything in here
    [Jim canYouLookAfterMy:cat];
    // myString still exists
    NSLog(myString);
}

When we reach the end of that code block, we would expect our iPhone to still know what myString means – if it didn’t then programming would be considerably harder than it is!

In the simplest case, like above, our variables have a fairly obvious lifespan (or scope): after we finish executing aSimpleFunction, myString will cease to exist. What about a more complicated situation though?

// This will do bad things to our memory
- (void) showAnAlert {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Remember me?"
                                              message:@"I'm an alert box"
                                              delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];
    [alert show];
}

The last thing that that code sample will do is display an alert, which we would expect to keep existing after the function has finished executing (and, indeed, until the user presses the OK button). The alert still occupies a space in memory outside of the function in which it was declared.

Garbage Collection

Computers generally have a limited amount of memory, though this amount will vary depending on what you’re using. The computer that I’m writing this blog post on has 6 GB. My iPhone 4, on the other hand, only has 512 MB. If you keep storing things in memory, eventually you run out of space.

On most systems (and in most languages) variables that have been declared, used and discarded will be automatically cleared from memory by a garbage collection process. On our iPhone and iPad, however, that doesn’t happen: Objective C does have the capability for garbage collection (it exists for OSX applications), but it is intentionally disabled in iOS to optimize performance.

In our example above, therefore, our alert gets stored in memory, but once we’ve dismissed it it will continue to exist. The piece of memory that refers to the alert will still have that object in it, but nothing in our code can access it, and nothing will come along and free up the memory. We have leaked it. If we leak enough objects, eventually we will have allocated all the memory we can and any attempt to declare something new will result in an out of memory exception – which in iOS causes your app to exit.

Memory Management

So that we can prevent ourselves from running out of memory – after all, we want people to use our app for as long as they can – we need to tell the operating system when our variable is finished with, so that it knows to free up that block of memory. In the simple (first) code sample above, it happens automatically, but clearly our second one doesn’t. We need to explicitly remove our alert from the memory. There are two ways of doing this:

[alert release];

and

[alert autorelease];

The first of these acts immediately, while the second would add our alert to a pool of things that we know will need to be released. The eagle-eyed among you will have noticed that in our original code sample, we called autorelease on our alert before showing it. For the most part, however, it’s better to use release in preference to autorelease as much as possible.

So when do we need to release our objects? A rule of thumb is that anything that we have previously created with an alloc call will need to be released (alloc means that we want to allocate a region of memory for our variable). The offending part of the code at the end of the last post, is:

// They're tired - how about a cup of coffee?
UIImage *image = [UIImage imageNamed:@"coffee.jpg"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[imageView setFrame:CGRectMake(14, 50, 293, 284)];
[window addSubview:imageView];

Our UIImageView was alloc‘d but never released. All we need to do is add:

[imageView release];

to the end of that code snippet and all will be well.

Hang on!

I said above that release acts immediately – surely if we release the imageView, it will stop existing right when we want to show a cup of coffee? Fortunately not. Every object has a retain count (where retain is the opposite of release) which tracks the number of things that are referring to it. Only when that count gets to zero will our object be cleared out of the memory. When we add the imageView to our window:

[window addSubview:imageView];

that increments the retain count by 1, and when we release the window, it will automatically release the imageView.

Danger!

So what happens if we release something too soon, for example:

// This will go horribly wrong
- (void) breakAnAlert {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Another alert"
                                              message:@"This will break"
                                              delegate:nil
                                              cancelButtonTitle:@"Oh dear"
                                              otherButtonTitles:nil];

    [alert release];
    [alert show];
}

In this function, we create an alert, then release it, and then try to show it. When we get to the last line, our variable alert is no longer linked to any particular piece of memory – calling show on it will throw an exception and exit our application!

More Danger!

Sometimes it gets very difficult (but never impossible) to avoid leaking memory – in particular, the iOS SDK is not flawless and there are some Apple-built functions that have inherent leaks.

For example, this time last year I was developing a cloud storage app that made some of your cloud files available offline by storing them on your iPhone or iPad, and which needed to keep track of how much space it had used. Checking the size of a file (using Apple’s API) leaked a tiny amount of memory; Looping through all the files checking them all leaked that small amount for each file; Putting a very large number of very small files onto my cloud account meant that my test iPad leaked its entire memory capacity and crashed the app in a shade under two seconds…

Summary

Memory management is definitely the part of iOS development that I found the most complicated – to a certain extent it’s something that you have to just get used to with practice, although Apple provide a number of tools as part of the SDK download to help you with it.

I hope that you have found this series of posts useful, interesting and, most of all, entertaining. As I mentioned back in my first post, the Apple documentation is generally very good indeed, with class documentation, tutorials, sample code and much more – including a guide to memory management. And if that fails, there’s always stackoverflow!

I’ve attached the source code for my hello world application for you to peruse and modify at your leisure.

Tags: , , , , , , , ,

Categories: Mobile, Technical

«
»

Leave a Reply

* Mandatory fields


eight + 6 =

Submit Comment