- Creating New Projects
- Building Hello World the Template Way
- Using the Simulator
- The Minimalist Hello World
- Converting Interface Builder Files to Their Objective-C Equivalents
- Using the Debugger
- Memory Management
- Recipe: Using Instruments to Detect Leaks
- Recipe: Using Instruments to Monitor Cached Object Allocations
- Analyzing Your Code
- Building for the iOS Device
- Detecting Simulator Builds with Compile-Time Checks
- Performing Runtime Compatibility Checks
- Pragma Marks
- Preparing for Distribution
- Over-the-Air Ad Hoc Distribution
- Submitting to the App Store
- Summary
Recipe: Using Instruments to Monitor Cached Object Allocations
When you load too much data at once, you can also run short of memory. Holding onto everything in your program when you are using memory-intense resources such as images, audio, or PDFs may cause problems. A strategy called caching lets you delay loads until resources are actually needed and release that memory when the system needs it.
The simplest approach involves building a cache from an NSMutableDictionary object. A basic object cache works like this: When queried, the cache checks to see whether the requested object has already been loaded. If it has not, the cache sends out a load request based on the object name. The object load method might retrieve data locally or from the Web. After the data is loaded, the cache stores the new information in memory for quick recall.
This code performs the first part of a cache’s duties. It delays loading new data into memory until that data is specifically requested. (In real life, you probably want to type your data and return objects of a particular class rather than use the generic id type.)
- (id) retrieveObjectNamed: (NSString *) someKey { id object = [self.myCache objectForKey:someKey]; if (!object) { object = [self loadObjectNamed:someKey]; [self.myCache setObject:object forKey:someKey]; } return object; }
The second duty of a cache is to clear itself when the application encounters a low-memory condition. With a dictionary-based cache, all you have to do is remove the objects. When the next retrieval request arrives, the cache can reload the requested object.
- (void) respondToMemoryWarning { [self.myCache removeAllObjects]; }
Combining the delayed loads with the memory-triggered clearing allows a cache to operate in a memory-friendly manner. Once objects are loaded into memory, they can be used and reused without loading delays. However, when memory is tight, the cache does its part to free up resources that are needed to keep the application running.
Simulating Low-Memory Conditions
One feature of the simulator allows you to test how your application responds to low-memory conditions. Selecting Hardware > Simulate Memory Warning sends calls to your application delegate and view controllers, asking them to release unneeded memory. Instruments, which lets you view memory allocations in real time, can monitor those releases. It ensures that your application handles things properly when warnings occur. With Instruments, you can test memory strategies such as caches, discussed earlier in this chapter.
Recipe 3-2 creates a basic object cache. Rather than retrieve data from the Web or from files, this cache builds empty NSData objects to simulate a real-world use case. When memory warnings arrive, as shown in Figure 3-14, the cache responds by releasing its data.
Figure 3-14. Instruments helps monitor object allocations, letting you test your release strategies during memory warnings.
The stair-step pattern shown here represents four memory allocations created by pressing the Consume button while using Instrument’s Allocation profiler. After, the simulator issued a memory warning. In response, the cache did its job by releasing the images it had stored. The memory then jumped back down to its previous levels.
Instruments lets you save your trace data, showing the application’s performance over time. Stop the trace and then choose File > Save to create a new trace file. By comparing runs, you can evaluate changes in performance and memory management between versions of your application.
Some SDK objects are automatically cached and released as needed. The UIImage imageNamed: method retrieves and caches images in this manner, as does the UINib nibWithNibName:bundle:, which preloads NIBs into a memory cache for faster loading. When memory grows low, these classes empty their caches to free up that memory for other use.
Recipe 3-2. Object Cache Demo
@implementation ObjectCache @synthesize myCache, allocationSize; // Return a new cache + (ObjectCache *) cache { return [[ObjectCache alloc] init]; } // Fake loading an object by creating NSData of the given size - (id) loadObjectNamed: (NSString *) someKey { if (!allocationSize) // pick your allocation size allocationSize = 1024 * 1024; char *foo = malloc(allocationSize); NSData *data = [NSData dataWithBytes:foo length:allocationSize]; free(foo); return data; } // When an object is not found, it's loaded - (id) retrieveObjectNamed: (NSString *) someKey { if (!myCache) self.myCache = [NSMutableDictionary dictionary]; id object = [myCache objectForKey:someKey]; if (!object) { if ((object = [self loadObjectNamed:someKey])) [myCache setObject:object forKey:someKey]; } return object; } // Clear the cache at a memory warning - (void) respondToMemoryWarning { [myCache removeAllObjects]; } @end