Channels ▼
RSS

Tools

Managing Memory on iOS


2. Use smaller resources.

Each time an iOS loads a resource, that resource has to take up memory. The resource may stay resident for the duration of a view or task or, in some cases, for the entire app session. Since loaded resources take up precious memory space, we must try to keep their sizes as small as possible.

Consider graphics files, for instance. They hold the icons and logos that appear on an iOS view. The preferred format is a 32-bit PNG, usually no larger than 64x64 square pixels (for an iPhone target). To prepare the graphics file, use a preprocess tool such as pngcrush or ImageOptim. Both resample and recompress the file for the smallest possible size and load speed. Usage notes for both tools are available on their respective websites.

Now consider text files. These hold text localized for a specific region. Their data should be rendered in serialized form, as opposed to Unicode or UTF-8. This can cut down the extraneous bytes needed for each text character.

Listing Three shows one way to handle serialized text. First, the method loadSerialText gets the path to the text resource foobar.text (line 13). Having a valid path, the method loads the resource into an NSData object (line 18). It then extracts the raw character bytes and uses them to create an NSString object (lines 22-28).

Listing Three

// Load the serialised text data
- (NSString *)loadSerialText
{
	NSString *tPth, tStr;
	NSData *tDat;
	NSUInteger tLen;
	char *tBuf;
	
	// prepare the default result
	tDat = [NSString string];
		
	// locate the text resource
	tPth = [[NSBundle mainBundle] pathForResource:@"foobar"
				ofType:@"text"];
	if (tPth != nil)
	{
		// read the resource data
		tDat = [NSData dataWithContentsOfFile:tPth];
		if (tDat != nil)
		{
		// extract the data bytes
		tLen = [tDat length];
		tBuf = malloc(tLen * sizeof(char));
		[tDat getBytes:tBuf length:tLen];
		
		// create the string object
		tStr = [NSString stringWithCString:(const char *)tBuf 
			encoding:NSNonLossyASCIIStringEncoding];
		
		// dispose the buffer
		free(tBuf);
		}
	}
	
	// return the retrieval result
	return (tStr);
}

// Save the serialised text data
- saveSerialText:(NSString *)aTxt
{
	NSString *tPth;
	NSData *tDat;
	NSUInteger tLen;
	char *tBuf;
	BOOL tChk;
	
	// prepare the character buffer
	tLen = [aTxt length];
	tBuf = malloc(tLen * sizeof(char));
	
	// initialise the buffer
	tChk = [aTxt getCString:tBuf 
		maxLength:tLen encoding:NSNonLossyASCIIStringEncoding];
	
	// locate the text resource
	tPth = [[NSBundle mainBundle] pathForResource:@"foobar"
				ofType:@"text"];
	
	// write out the buffer
	tDat = [NSData dataWithBytes:(const void *)tBuf 
		length:tLen];
	tChk = [tDat writeToFile:tPth atomically:YES];
	
	// dispose the buffer
	free(tDat);
}

The method saveSerialText gets an NSString for input and extracts the raw character bytes (lines 49-54). It locates the resource file foobar.text (line 60) and stores the raw bytes into an NSData object (lines 61-62). Finally, it writes the object itself to the resource (line 63).

3. Avoid pre-fetching.

Some developers let their applications load their resource at launch time. Their intent is to avoid reloading often-used resources and to make those resources readily available. While this practice may be sensible with OS X apps, it will make an iOS app a memory hog.

A better way is to load those resources on demand. Then discard the resource when it is no longer needed. Doing so cuts down the memory needed by the app, and avoids inducing a low-memory state.

4. Optimize for size.

Executable size can also influence the overall memory footprint. When an iOS app launches, it loads as much of its executable code into memory as possible. The code stays resident until the application terminates.

Thus, it is important that we keep the executable size as small as possible. The best way to achieve this is to compile the project with an mthumb option. This option directs GCC to optimize the final executable by size. We get a smaller executable size, at the cost of a slight decrease in performance.

Figure 3 shows how to prepare the compile settings. First, choose "Edit Project Settings" from Xcode's Project menu to get the settings dialog. Locate the group "GCC 4.2 - Code Generation," and check the option "Compile for Thumb." If the option is not set, click to set the adjacent checkbox.

Figure 3.

Most iOS projects benefit greatly with size optimization, but there is a small group of projects where the costs of optimization may outweigh its benefits. These projects perform a large number of floating-point tasks while using arm6 opcodes. If their executables are optimized for size, their runtime performance may be severely reduced. In this situation, switching off the mthumb option may be a better choice.

Managing Memory

Apple also provides three guidelines on how an iOS app should manage its share of memory. These guidelines take into account the lack of garbage collection and the lack of volatile paging. So let us go through them, shall we?

1. Reduce the number of autoreleased objects.

Naturally, objects marked for autorelease linger longer that those with normal retain/release cycles. When there are too many autoreleased objects, the autorelease pool will need more memory to maintain its collection of objects.

Consider the sample method back in Listing Two. This method uses three autoreleased objects to perform its task. Two of those objects, tNom and tTmp, (lines 9,12) are valid only with the method scope and are disposed of on exit. The third object, also named tNom, (line 20) stays valid outside scope due to being a return value. Its disposal is then left to the calling method.

Now consider the sample method in Listing Four.

Listing Four

// Simple word parser
- (NSArray *)str2word:(NSString *)aTxt
{
	NSArray *tWrd;
	NSCharacterSet *tSet;
	NSUInteger tLen;
	
	// parametre check
	tLen = [aTxt length];
	if (tLen == 0)
		tWrd = [NSArray array];
	else
	{
		// prepare the set of tokens
		tSet = [NSCharacterSet 
			characterSetWithCharactersInString:@".?:!,; *&()%#@_-{}[]"];
		
		// divide the text into words
		tWrd = [aTxt componentsSeparatedByCharactersInSet:tSet];
	}
	
	// return the extraction results
	return (tWrd)
}

At first, this one appears to use only two autoreleased objects (lines 15, 23). Object tSet is valid only within the method scope, while object tWrd stays valid outside scope. What is not obvious is that tWrd is an array of NSString objects. Since those objects are made by the factory method componentsSeparatedByCharactersInSet (line 19), they are all marked for autorelease. So if the input NSString argument aTxt has a particularly long string, the autorelease pool may end up using more memory to hold that array.

2. Place limits on resource loading.

Most iOS apps load their resources in their entirety. This is not a problem when the resource itself is reasonably small. But suppose the resource is at least a quarter the size of available memory. Then loading it will take up more space, leaving less memory for other tasks.

A better way to deal with large resources is to load the data piecemeal, again on demand. Use the library routines mmap() and munmap() to copy only the needed portions into memory. Alternatively, you can prepare the resource as a database. Then use Core Data to access each resource portion as a record.

3. Avoid unbound problem sets.

Unbound sets are sets with no discernible limits. They could be data structures that kept growing with every use. They could be routines that use repetition to perform their tasks.

Listing Four was a good example of an unbound set as a data structure. There, the data supplied by the NSString argument aTxt dictates the size of the NSArray object tWrd. If the string data consists of hundreds of words, then the array may get hundreds of entries. If the data has just a dozen or so words, the same array will have less entries.

Thus, there is no easy way to determine the final array size by just examining the input string. Perhaps a better solution is to rewrite the routine so it extracts each word in situ.

Listing Five contains an example of an unbound set in the form of a routine. This one gives the root value nearest to the argument aX. It uses a basic Newton-Raphson algorithm (lines 7-9) to compute a possible root for a given polynomial. If the root does not converge, the routine calls itself, passing along the computed value as input (lines 12-14).

Listing Five

// Computing for a polynomial root
- (float)doRoot:(float)aX
{
	float *tZer, tDel, tNum, tDen;
	
	// calculate using Newton-Raphson algorithm
	tNum = poly_func(aX);
	tDen = poly_deriv(aX);'
	tZer = aX - (tNum / tDen)
	
	// validate the result
	tDel = abs(tZer - aX)
	if (tDel > 0.0000001)
		tZer = doRoot(tZer);
		
	// return the zero result
	return (tZer);
}

Since the routine uses recursion, its stack grows with each computation. This becomes a problem when the stack needs more memory than what is available. Perhaps a way to avoid this problem is to rewrite the routine to use iteration, not recursion.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<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

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video