Filed under “things I didn’t realise anyone else would find useful”.
The Hybrid App
The second iOS app I worked on was a hybrid app, and I became a master of the UIWebView. It looked native. But it didn’t quite feel native because… performance.
The Location Tracker
This one had a myriad of issues. In theory, the API says you can just register for location updates with accuracy. In practise, if the accuracy is… accurate (<150m, if I recall correctly) the GPS turns on, stays on, and the battery rapidly runs down.
Even if the phone is sat on your desk, not moving (one would have thought the accelerometer would be useful here, but no).
So the answer was to operate with the GPS off most of the time, and then after a notification turn it on again for a limited period.
Then there is the sheer volume of updates you get. If you just send each one with a network call, then… that’s a lot of network calls.
So instead at each point you have to decide, should I send the update now, or wait for better accuracy. Meanwhile, cache the unsent ones.
Now you have an app that is running in the background, and being awoken when there’s an update. But if the app gets kicked out of memory, all your unsent locations are lost.
Fun story – we had it just about working and then I went to Kangaroo island which has terrible cell-phone service, and was taking loads of pictures. So even though I was intermittently on the network, hardly any locations were being sent because the camera was causing the app to be kicked out of memory.
Basically: we had to implement persistent storage for location tracking to work.
The Image Processor
This app was handling a quantity of data (pixels) and had time and space problems.
To reduce the memory usage, I got rid of as many Objective-C objects as I could, and replaced them with C. So the NSArray of NSNumbers became a CGFloat. Stopped using UIColor and started working with raw RGB values.
Memory footprint reduced by around 66%, and the app became much, much faster.
(I wrote all the tests on slower and easier to read code, which helped a lot with this process).
Major problem here was how little information there was on the internet. I’m not an expert C coder, and I would look for how to do something in C within Objective-C and the answer was typically “there is almost certainly no need for you to do this“.
The other performance improvement I made was to break up the processing work and do some pre-processing in the background thread, so that the wait is split into two very short waits, as opposed to one, slightly longer, wait.
Things I’ve Learned
Find the One Thing
There’s not, in my experience (assuming you’ve been sensible around the basics of ARC, table views etc) any need to go through your code looking for “things to optimise”. iOS is mostly pretty good. The problems I described above all occurred in the most non-standard places of the app. Getting a deep understanding of how that thing worked was how it was fixed (aside from the first one, which I still think is just weird).
The Profiler is Not That Scary
Memory allocations is the one I’ve been working with most recently, but there is also a way to profile which parts of your code most time is spent in. I can see this being really useful. I used to be really intimidated by the profiler, but having spent more time with it, it’s OK [helpful tutorial].
Performance Tests are Easy, but Slow
I started adding performance tests, typically at stress sizes (10x the data I expect to process), using the
(void)measureBlock:(void (^)(void))block function in XCTestCase [more info]. Each performance test runs 10x to give variance, and you can save a per-device baseline. Because these are my most time consuming tests I don’t want to run them when I’m developing against my tests so I moved them out into a separate target (that takes a few minutes to run) so that my core test target runs faster (<30 seconds).