iPhone responsiveness and memory usage
I recently answered a question on a private mailing list about how to make a network - based (XML parsing and such) iPhone application more responsive. I've been encouraged to post it here by a few folks (Thanks, guys! You know who you are.). So, I figure "why not?" Here you go. (Slightly modified)
The original question (paraphrased): I have an iPhone application that downloads XML data from the web, parses it, and loads it all into memory. I notice that my app is a little sluggish and that it crashes as odd times, probably due to having too much information in memory. There are other apps that do this kind of thing on the app store and I notice that many of them are more responsive than mine. How does one make one's "network cloud-based" app more responsive?
My Answer
You're running into a few of the more common problems with embedded programming. Here are a few little tricks that have helped me in general (done a little embedded Linux programming, too) and on the iPhone.
Put breakpoints in memory warning methods and figure out when (and why) it's happening (again, on the device). Is it when you download data, load view A, then view B? Can you release view A's data when it goes off the screen to keep the memory footprint from growing? Do you need the whole tree for view B? Can you download it all, dump to disk, and load lazily? Blah blah blah. This stuff can get tricky, but try the obvious things first. Find out what is causing the memory ceiling problem and you're well on your way. :)
Using the LLVM/Clang static analyzer has saved me a lot of headaches as well. It's easy to forget that you have a web view somewhere with a bunch of cached data floating off screen causing the device to go bork bork...
Cache yesterday's data, load, and display it before going out to the network for today's data. Use a progress indicator and some other UI indicator to let the user know that you're updating from the network and that they're looking at what may be old data. Do this asynchronously. When it comes to displaying data from the network, sometimes you have to fake it 'til you make it. You don't have to rewrite Google gears or anything, just throw everything you need from the server onto disk and reload it when the app launches.
Gus Mueller's FMDB rocks for SQLite work. I have a version with embedded Dtrace probes if anybody wants it. (This will have to be the subject of a later article after I find some time to update my version to Gus' latest version and to send it to him to look at / integrate if he wishes first...)
The original question (paraphrased): I have an iPhone application that downloads XML data from the web, parses it, and loads it all into memory. I notice that my app is a little sluggish and that it crashes as odd times, probably due to having too much information in memory. There are other apps that do this kind of thing on the app store and I notice that many of them are more responsive than mine. How does one make one's "network cloud-based" app more responsive?
My Answer
You're running into a few of the more common problems with embedded programming. Here are a few little tricks that have helped me in general (done a little embedded Linux programming, too) and on the iPhone.
Regarding bandwith / data size / responsiveness as a result of downloading data
You'll be surprised that the actual downloading of anything but pretty large amounts of data is pretty fast by itself. The part that is slow is the stuff that happens before the download even starts. Edge / 3G latency sucks and even DNS lookup is really really really (really?) slow on the iPhone in almost all circumstances AFAICT. I've found that strategies that download more data at a time while discarding unneeded data that you're getting on the fly, rather than making multiple requests for specific small chunks of data has been a performance win. That's a little counter intuitive.Regarding memory
There is a reason the NSXMLDocument doesn't exist on the iPhone. You can get away with throwing a smallish XML tree into memory and go ahead and do it if it simplifies your life for small data sets, but the combination of larger XML trees in memory and whatever else (including UI) you have cached in memory can cause you to hit the memory ceiling at weird times; that's probably what's happening when your app crashes. The app will get terminated if you don't release memory when you get these memory warnings. Take a look at apples "Books" SQLite example. They're not doing exactly what you're looking for there (no network code), but the handling of memory works as they're doing it. They load data from a SQLite database lazily, but keep that data in memory until the application closes, until the owning object is deallocated, or when memory gets scarce. When the app gets those fun memory warnings, they save any changed data and release everything that they can get back out of the database later. This is similar in theory to the lazy loading of views we've all grown accustomed to (and that is partly handled for you). When a view controller gets a memory warning, its views are released by default if they don't have a superview, which is why you have to figure out if the view is still there every time you (re)load it. Doing something similar with your XML data by saving it somewhere when you get a memory warning and releasing the associated data structure, reloading it piecemeal from disk as needed (be it an SQLite database or what-have-you) works pretty well and appears to be the recommended approach. Ultimately, figuring out how much and which data to put on "disk" and/or in memory (and when) is the biggest PITA in embedded programming, but it's also the place where you're going to get the responsiveness you're looking for. http://furbo.org/2008/08/27/dealing-with-memory-loss-the-cleanup/Regarding UI responsiveness
Don't use a lot of subviews and avoid opacity. Instead draw the views yourself... Draw once, if possible. ( http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/ )Other tricks
Use Instruments and Shark with the code running ON THE DEVICE to see how much memory you're using, how much disk access you're doing, and what is actually going on timing-wise. Optimize those specific areas profiling the simulator (faster edit, compile, test cycle). Rinse (on the device) and repeat.Put breakpoints in memory warning methods and figure out when (and why) it's happening (again, on the device). Is it when you download data, load view A, then view B? Can you release view A's data when it goes off the screen to keep the memory footprint from growing? Do you need the whole tree for view B? Can you download it all, dump to disk, and load lazily? Blah blah blah. This stuff can get tricky, but try the obvious things first. Find out what is causing the memory ceiling problem and you're well on your way. :)
Using the LLVM/Clang static analyzer has saved me a lot of headaches as well. It's easy to forget that you have a web view somewhere with a bunch of cached data floating off screen causing the device to go bork bork...
Cache yesterday's data, load, and display it before going out to the network for today's data. Use a progress indicator and some other UI indicator to let the user know that you're updating from the network and that they're looking at what may be old data. Do this asynchronously. When it comes to displaying data from the network, sometimes you have to fake it 'til you make it. You don't have to rewrite Google gears or anything, just throw everything you need from the server onto disk and reload it when the app launches.
Gus Mueller's FMDB rocks for SQLite work. I have a version with embedded Dtrace probes if anybody wants it. (This will have to be the subject of a later article after I find some time to update my version to Gus' latest version and to send it to him to look at / integrate if he wishes first...)