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


