At the beginning of this article, I said that a lot of traditional optimizations trade speed and memory usage. In the mobile space, this trade remains very important. The power usage for RAM is pretty much constant. In theory, a device could turn off some of its RAM chips when they're not in use, but I've never seen a machine that actually does this.
In contrast, the GPU uses vastly more power when active than when idle. For a laptop CPU, this difference can be between 1[nd]2W and 12[nd]35W. For a mobile CPU, the jump is similar, although smaller in absolute terms. Therefore, it's important to cache as much as you possibly can on a mobile device. This is one of the main reasons that the iPhone uses the Core Animation model for rendering, rather than the older model inherited from NeXT. The old NeXT workstations had a much bigger screen than the iPhone, but only a 25 or 33 MHz CPU and 8MB of RAM. The frame buffer took a large proportion of this memory; any time you moved a window, it triggered a redraw event.
On the iPhone, most views render to layers (off-screen textures), which are composited together for drawing. As long as video memory is available, these layers don't need to be redrawn. Showing a user interface element flying across a window on the iPhone uses very little CPU, while the same effect on a NeXT workstation would have lots of redraw events for every frame. The cost of this reduction in CPU usage is more RAM.
Ideally, a mobile device's memory should always be full. Anything that you can cache for later, you shouldright up until the point where you run out of memory. Then everything changes.
When you run out of memory space on a desktop, the operating system spills some pages to the disk. The same thing happens on most laptops. But a lot of handhelds won't do anything like this. Running out of memory on a laptop means spinning up the disk (bad for power usage), but running out of memory on a handheld often means that your app gets killed.
There are a few ways of avoiding this problem. Often, APIs are provided by the toolkit. On the iPhone, for example, the NSCache class works like a dictionary, but its contents will be removed when the system is low on memory. You can keep putting things into the cache, and they'll stay there as long as space is available.
Another alternative is to use lower-level functionality. Although a lot of handheld devices ship with swap space disabled, often they still provide the same underlying mechanism. The mmap() system call (or equivalent) lets you define a region of memory that's backed by a file. This technique is especially useful for resources that you're only reading. If you use the read() system call (or equivalent), you have to allocate a buffer in memory. The OS copies the relevant blocks of the file into its buffer cache and then copies them again into the memory that you allocated.
With a mapped file, you eliminate this second copy. The operating system just maps the pages containing the cached copy of the file into your address space. Best of all, when it's short on physical memory space, the OS simply marks those pages in your virtual address space as not present, fetching them from disk the next time that you access them.
Although not designed as a mobile application, the Dovecot POP and IMAP server uses this technique very effectively. It maps files for the mailbox indexes and typically uses only a tiny amount of memory when idle. When active, it can access the files as if it had a local copy in memory (which it does, while accessing them), but as soon as it stops touching them for a bit, the OS can throw them out again.
Of course, when you're low on memory, this approach still causes some extra access to persistent storage, which isn't idealbut it's better than having your app die, and can allow you to go right up to the edge of available memory.