Software products slated for the iOS market have to be frugal in their use of memory. iOS devices such as the iPhone and iPad have limited physical memory, much less than their flash storage. Using Xcode can help design frugal code and subject source files to static analysis. You can also use its Instruments tool to track down memory problems at runtime. In this article, I discuss some common memory problems and how they can affect a typical iOS app. I show you how to detect these problems with the aforementioned tools and some ways of fixing them.
You will need a working knowledge of ANSI-C, Objective-C, and Xcode. The sample project needs version 3.x (or newer) of the Xcode development suite.
Types of Memory Problems
Most memory problems are one of four types. The first type is the dangling pointer. This is an object or data pointer that still refers to a deallocated memory block. The block may still have valid data, but the data can "disappear" at some point in time. Attempts to access a dangling pointer may lead to a segmentation fault (EXC_BAD_ACCESS
or SIGSEGV
). A dangling pointer should not be confused with a NULL
, which is defined as ((void *) 0)
.
Consider the snippet in Listing One. Here, class FooProblem
has a single property (objcString
) and a single action method. The action method, demoDanglingPointer:
, initializes objcString
to an empty NSString
(line 18). Then, in a separate code block, it creates an instance of NSMutableString
(line 20). It sends the instance a release message (line 22), but also assigns that same instance to objcString
(line 25). Once the code block exits, deallocation occurs and objcString
is left holding a dangling pointer.
Listing One
// -- FooProblem.h @interface FooProblem : NSObject { // -- properties:demo NSString *objcString; } // -- methods:demo:actions - (IBAction) demoDanglingPointer:(id)aSrc; @end // -- FooProblem.m @implementation FooProblem - (IBAction) demoDanglingPointer:(id)aSrc { NSString *tBar; objcString = [NSString string]; { tBar = [[NSMutableString alloc] initWithString:@"foobar"]; //... do something else [tBar release]; } //..do something else objcString = tBar; } @end
Now consider now the snippet in Listing Two. This variant of FooProblem
declares a C struct
named FooLink
(lines 1-6). In its demoDanglingPointer:
action, it declares the local variable tTest
and assigns the latter the output from method danglingPosix()
(line 26). The danglingPosix()
method creates a local instance of FooLink (tBar)
inside a code block (lines 38-41). It assigns values to the struct
fields and sets the local tFoo
to tBar
(line 42). But just before the code block exits, danglingPosix()
disposes of tBar
with a call to free()
(line 44) and returns the pointer held by tFoo
(line 48). The result local tTest
now has a dangling pointer.
Listing Two
typedef struct Foo { char *fFoo; unsigned int fBar; struct Foo *fNext; } FooLink; // -- FooProblem.h @interface FooProblem : NSObject { // -- properties //... } // -- methods:demo:actions - (IBAction) demoDanglingPointer:(id)aSrc; - (FooLink *)danglingPOSIX; @end // -- FooProblem.m @implementation FooProblem - (IBAction) demoDanglingPointer:(id)aSrc { FooLink *tTest; tTest = [self danglingPOSIX]; // ...do something else } - (FooLink *)danglingPOSIX { FooLink *tFoo; // initialise the output result tFoo = nil; { FooLink *tBar; tBar = malloc(sizeof(FooLink)); tBar->fFoo = "foobar"; tBar->fBar = 1234; tFoo = tBar; //... do something else free(tBar); } // return the string result return (tFoo); } @end
The next type of memory problem is the double free. This occurs when a code routine tries to dispose of an object or structure that has already been disposed of. Disposal need not happen in succession, so long as it affects the same pointer. A double free also leads to a segmentation fault, followed by a crash.
Listing Three is a classic example of a double free. The action method demoDoubleFree:
creates an instance of FooLink
and populates its fields (lines 3-5). It sends the instance to the method doubleFreePOSIX:
, which updates the two fields (lines 14-15). But then doubleFreePOSIX:
disposes of the FooLink
instance with a call to free()
(line 18). When it returns control to demoDoubleFree
:, demoDoubleFree:
also disposes of the same FooLink
structure using free()
(line 8).
Listing Three
- (IBAction) demoDoubleFree:(id)aSrc { tFoo = malloc(sizeof(FooLink)); tFoo->fFoo = "Foobar"; tFoo->fBar = 12345; [self doubleFreePOSIX:tFoo]; free(tFoo); } - (void)doubleFreePOSIX:(FooLink *)aFoo { //...do something else aFoo->fFoo = "BarFoo"; aFoo->fBar = aFoo->fBar + 123; //... do something else free(aFoo); }
Listing Four shows another double free example. The action method demoDoubleFree:
creates and adds an NSNumber
instance to the mutable array property objcArray
(lines 23-24). Then it invokes the method doubleFreeObjC
. This method parses the array property and uses its entries to create an NSString
object (line 39-40). Later, it sends a release message to each entry (line 44). If the entry is the NSNumber
object, a double free error occurs. This is because the NSNumber
object was marked for autorelease. An explicit release interferes with the autorelease pool's attempt to dispose of the object.
Listing Four
// -- FooProblem.h @interface FooProblem : NSObject { // -- properties NSMutableArray *objcArray; } // -- methods:demo:actions - (IBAction) demoDoubleFree:(id)aSrc; // -- methods:demo:utilities - (void)doubleFreeObjC; @end // -- FooProblem.m @implementation FooProblem // ...truncated for length - (IBAction) demoDoubleFree:(id)aSrc { NSNumber *tNum; tNum = [NSNumber numberWithLong:rand()]; [objcArray addObject:tNum]; //...do something else [self doubleFreeObjC]; } - (void)doubleFreeObjC { NSString *tText; id tObj; tText = [NSString string]; for (tObj in objcArray) { tText = [tText stringByAppendingFormat:@"/@", tObj]; //...do something else [tObj release]; } } @end
A memory leak is another typical memory problem. It occurs where a routine fails to dispose of its objects or structures. The failure may be due to an error or exception, or it may be due to poor code design. A continuous memory leak can lead to a low-memory condition. At best, it could cause iOS to terminate the offending app. At worst, it could force users to perform a hard reset.
Listing Five is one example of a memory leak. This variant of FooProblem
creates an empty NSMutableArray
instance and assigns it to the property objcArray
(lines 17-18). Its dealloc
method, however, does not send a release message to objcArray
. The result is, of course, a memory leak, even if the array object remains empty.
Listing Five
// -- FooProblem.h @interface FooProblem : NSObject { // -- properties NSMutableArray *objcArray; } @end // -- FooProblem.m @implementation FooProblem - (id)init { if (self = [super init]) { // prepare the following properties objcArray = [[NSMutableArray alloc] initWithCapacity:1]; return (self); } else // the parent has failed to initialise return (nil); } //...other methods go here - (void)dealloc { //...do something else // pass the message to the parent [super dealloc]; } @end