Channels ▼


Managing Memory on iOS

One challenge faced by new iOS developers is how to work with the limited memory on Apple's handheld products. Products must optimize memory use, avoid leaks, and reduce the overall footprint.

This article explains how an iOS application should manage its allocated memory. It describes the lifecycle of a Cocoa object and how that cycle differs on iOS, and then explains how to reduce the memory footprint and to prevent memory leaks. It also explains how to detect and react to a low-memory signal from iOS.

Readers must have a working knowledge of Xcode and Cocoa.

Life of a Cocoa Object

A typical Cocoa object undergoes three distinct stages in its lifecycle (Figure 1).

Figure 1.

First, the object is created. This is done by sending an alloc message to the appropriate class. The class reserves the memory needed by the object and returns the object itself as a result. For example, the statement below creates an instance of the NSString class and stores it in the variable tFoo:

	tFoo = [NSString alloc];

If the allocation fails due to insufficient memory, the class will return a nil object.

Next, the object is initialized. This is where it assumes a default state and value. It is where the object defines its delegates and prepares its parent. Initialization happens when the object gets an init message. Using our NSString example, this next statement initialize the object to a null string:

	[tFoo init];

Now most Cocoa classes have custom methods with which to initialize their respective objects. And many of those methods have two or more arguments to pass data or states to the object. In the case of NSString, for instance, the method initWithString gives the object with an initial string value:

	[tFoo initWithString:@"foobar"];

Finally, the object reaches a time when it has to be disposed of. It cleans up after itself and frees up all the memory used by its properties and methods. Disposal usually happens when the object gets a dealloc message:

	[tFoo dealloc];

But this could cause problems, especially when two or more other objects have to work with the affected object. Thankfully, there is a better way of disposing a Cocoa object, and it involves the use of reference counts.

Objects on Reference

By default, a newly created object has a reference count of 1. When that count hits 0, the object starts disposing of itself. In short, it literally self-destructs.

So to dispose the object, send it a release message:

	[tFoo release];

This will decrease the object's reference count by 1. The object runs its dealloc code, as well those of its parent. Once the object is disposed of, its variable then points to a "bad" address.

But suppose we want to add the object to a collection, or we want to use the object as a return value. For these cases, we must keep the object from self-destructing on its own.

To prevent disposal, send a retain message to the object as follows:

	[tFoo retain];

This will increase the object's reference count by 1. With a count greater than 1, the object stays valid and will be unable to dispose of itself.

Now it is important that the object gets an equal set of retains and releases (Figure 2). If the number of retains exceeds those of releases by at least one, the end result is a memory leak. Conversely, if the number of releases is greater, the result is a bad access error.

Figure 2.

An alternative way to disposing an object is to mark it for autorelease. The marked object goes into an autorelease pool, created just before the application starts its event loop. Periodically, the pool checks its collection of objects, locating those with a reference count of 1, which are out of scope. When such an object is found, the pool disposes of it with a release message.

To place an object into the autorelease pool, send it an autorelease message:

	[tFoo autorelease];

To prevent the pool from disposing of the object prematurely, use the retain message as described earlier.

Most Cocoa classes, however, can produce objects already marked for autorelease. This is done by using any one of the factory methods from each class. Consider again our NSString example. Its factory method stringWithString will create and initialize an instance of NSString. Furthermore, that same instance will be slated for autorelease.

	tFoo = [NSString stringWithString:@"foobar"];

The iOS Difference

Now the same Cocoa object will have a similar lifecycle on iOS. Creating and initializing an object use the same messages. Retention and disposal are also the same. This is not surprising, of course, since iOS is a variant of MacOS X, but one optimized for handheld use.

iOS has its own notable quirks. First, it has a smaller amount of physical memory than its desktop cousins. Physical memory can be as small as 256 MB on an iPhone 3GS, or 512 MB on an iPad2. The system itself takes up at least 64 MB of that memory for its own needs. This leaves a smaller amount for all the applications to share. Flash memory, which figures in the gibabyte range, is used solely for storage. Furthermore, that same physical memory is not user-upgradeable.

Second, though iOS has the same virtual memory engine as OS X, its engine does not write out inactive resources to volatile pages. Instead, it expects each iOS app to dispose of its unneeded resources and free up the occupied memory.

Third, iOS favors the active application session, which has the user's immediate attention. It will signal background tasks and inactive apps to frequent memory purges. In severe cases, iOS may terminate the apps themselves.

Finally, as of this writing, iOS does not have any garbage collection service. Instead, it expects each application to manage its memory share properly and frugally. Again, iOS will terminate those apps that habitually hog the memory store.

On Optimizing Footprint

Now the first step to prepare an application for iOS is to reduce its memory footprint. Too large a footprint can degrade an application's performance and that of its host system. It reduces the amount of available memory and marks the application for immediate termination.

Apple offers four guidelines on how to reduce an app's memory footprint. Follow these guidelines closely when working with your iOS projects.

1. Locate and fix all possible memory leaks.

As stated earlier, a memory leak happens when a Cocoa object fails to dispose of itself properly. It may be due to one too many retains, leaving the object with a reference count of 1 or more. It may even be due to the object using malloc() to allocate itself some memory, then failing to call free() to release said memory.

Consider the sample class in Listing One. This class defines a typical view controller. It declares three properties, which link the controller to three widgets on its window view (lines 5-7). Then it uses the @property and @synthesize keywords to declare and define the accessors for those outlets (lines 11-13, 23-25).

Listing One

@interface MyViewController : UIViewController <UITextFieldDelegate>
	// -- properties:outlets
	UITextField *textField;
	UILabel *label;
	NSString *string;

// -- accessors:outlets
@property (nonatomic, retain) IBOutlet UITextField *textField;
@property (nonatomic, retain) IBOutlet UILabel *label;
@property (nonatomic, copy) IBOutlet NSString *string;

// -- methods:actions
- (IBAction) changeGreeting:(id)sender;

@implementation MyViewController
// -- accessors:outlets
@synthesize textField;
@synthesize label;
@synthesize string;

// -- The user has entered new text data
- (IBAction) changeGreeting:(id)sender;
	// read the entered string
	self.string = textField.text;
	// prepare the name string
	NSString *nameString = string;
	// should a default name be used?
	if ([nameString length] == 0)
		nameString = @"World";
	// prepare the string data
	NSString *greeting = [[NSString alloc] initWithFormat:@"Hello, %@!"
						  , nameString];
	// display the string
	label.text = greeting;
	[greeting release];

// The user has pressed the <Return> key
- (BOOL)textFieldShouldReturn:(UITextField *)aFld
	// check the calling widget
	if (aFld == textField)
		[textField resignFirstResponder];
	return (YES);

// -- The view has been disposed
- (void)viewDidUnload 
	// pass the message to the parent
    [super viewDidUnload];

// -- The controller is about to be destroyed
- (void)dealloc 
	// dispose the following outlets
	[textField release];
	[label release];
	[string release];
	// pass the message to the parent
    [super dealloc];

Note the three lines in the controller's dealloc routine (lines 75-77). They send a release message to each of the outlets. But what if we forget to add these three lines? The result will be each outlet remaining valid after the view controller has self-destructed. We then get a small leak, which grows every time the iOS app recreates and disposes of the same controller.

Next, consider the sample method in Listing Two. This method gets an NSString input, which holds a path string. First, the method copies the string into the local tNom (line 9). It uses the instance method pathComponents to separate the path into its component items (line 12). The separated items are then returned in the form of an NSArray. Next, the method reads the last item of that array (15). If the array has only two entries, a root separator and name, the method prepares an empty string (line 17). And it returns the result as an NSString object.

Listing Two

// Return the file or directory name 
- (NSString *)getItemName:(NSString *)aPth
	NSString *tNom;
	NSArray *tTmp;
	NSInteger tIdx;
	// create a string object
	tNom = [NSString stringWithString:aPth];
	// extract a path item
	tTmp = [tNom pathComponents];
	tIdx = [tTmp count]
	if (tIdx > 1)
		tNom = [tTmp objectAtIndex:(tIdx - 1)];
		tNom = [NSString string];
	// return the extraction result
	return (tNom);

Note that all NSString instances are made with calls to factory methods. This means these instances are marked for autorelease. But suppose we send a retain message to the final instance:

	[tNom retain];

At first, this looks innocuous. After all, it only increases the reference count by 1. But if we fail to send a matching release message, this NSString instance will remain in the pool, taking up precious memory, thus creating a memory leak.

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags


<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags


<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

