One thousand eighteen bus stops
… or how
[UITableView reloadData] kicked my butt
The SeattleBus application includes a database with 1018 stops in it. Those were all the stops that I had with geolocation data and to which the MyBus web service responded with stop information. I started with 1211 data points, but just around 15% of the bus stops didn’t return any data.
One of the views in SeattleBus is a list of all the stops – and that’s where I felt the pain. In fact, it wasn’t until after 1.0 shipped that I realized exactly where the pain was coming from. It isn’t interesting caching or slow lookups from SQLite – it was from invoking
UITableView reloadData where the data had 1000+ elements. Loading 100 or even 300 was a pretty reasonable delay. You’d notice it, but it wasn’t too bad. 1000 just pushed it over the edge to a good few seconds. Not to mention the UI component of trying to scroll through 1000 items on the iPhone. Man, that’s just painful. It screamed for (and I implemented) a search mechanism to make it a more useful view.
At WWDC this year, they warned us that reloadData was expensive. Ooooh yeah. One of the suggestions that I heard for resolving that was to load a subset of the data (say 100 rows, which isn’t too bad of a wait) and then using the method
UITableView insertRowsAtIndexPaths:withRowAnimation: to inject additional rows using a background thread of when the user gets to where they need them. When I’d do a search and get back anywhere from 8 to 100 items (typical search in my tests), the reload was nicely fast. It was when I cancelled the search and reloaded the original view that everything went to hell. After reseting my model objects like a good programmer, I’d invoke
[UITableView reloadData] like I was used to doing on a desktop Mac’s NSTableView. Then (on the device – the simulator was plenty fast), I’d wait.
Kris Markel came up with an interesting solution when cobbling together a search example for me (now That’s a beta tester!). Instead of preloading the data he was loading it in from the database in the UITableView data source method
tableView:cellForRowAtIndexPath:. Ahhhh, right. I didn’t follow that path exactly (although I was darned tempted to), but instead implemented caching so that once I did that particular load, I didn’t have to re-do it again and again. I’ve got the application set to blow that cache away on a low memory warning, but it’s made a lot difference.
The next step there will be to load a subset up front and then populate that cache in the background with NSOperation or something equally interesting for a background thread. There’s no reason the user should have to wait through the 2.7 seconds of loading time for the whole kit.