Rock-Solid iOS App Stability
A couple years ago, I decided to dabble in iPhone app development. I wrote a little daily fitness routine tracker and tried to sell it for 99¢. It didn’t sell worth a damn, so I made it free for more than a year, and just recently I let it drop off the App Store so I could avoid the annual $99 developer fee. Maybe it will come back someday if I think of a better idea for an iPhone app and I’m paying the annual fee again.
But now for the interesting part of the story. The app had to have a way to store the user data in a file, so I just made up a file format. This byte meant this; that byte meant that; some data structures were variable length. Nothing too terribly complicated — just what was needed to store the user’s data for this app.
One day while testing the near-finished app, a funny thing happened. One of the scrollable lists in the app suddenly became defective. It still functioned, but the contents were obviously wrong. And I realized by looking at it that the likely culprit was corrupted user data. But when did it get corrupted? And by what code in my app? Suddenly, the enormity of diagnosing the problem made me feel a little ill.
And I felt a little more ill when I realized something else: There’s a lot of code in my app that makes changes to the user data. If there’s a bug somewhere that caused this problem to occur, even if I find it and fix it, how will I know it’s the only one? What if there are more? And what if I introduce still more bugs while making changes or adding features to the app? And worst of all: What if even one of these bugs slips out in the released product, and users lose everything the’ve done in the app, after weeks or months of use? That’s totally unacceptable. I don’t want that to happen at all.
I realized that I had to come up with some way to make my user data absolutely, rock-solid stable. But how? The only way I could think of that seemed like it would work would be to completely re-invent my user data as an XML-based format, then use somebody’s already-written, XML manipulation package to apply all changes to the content of the user data.
But this plan, even assuming it gave me the desired user-data stability, seemed like too big of a change to an app that was already written and in the testing phase. And it also seemed like an uncomfortable transfer of too much of the functionality to somebody else’s code over which I have no control. I.e., I can’t make it run fast if it’s slow; I can’t make it power-efficient if it’s a battery hog; I can’t bug-fix it if it turns out to need that, and I can’t make XML a tight, memory-efficient format if XML just isn’t.
And even if I did all that, couldn’t the user data still become corrupted? Valid XML doesn’t automatically mean valid user data, according to what my app needs to store there.
So what to do? I puzzled over it for a few days and then came up with a solution.
validateUserData
The user data was a single block of memory (variable size) that gets written to the app’s file space as a single, binary file, and read entirely into memory when the app loads. So it’s always there in memory, and I can examine it any time I want.
So I wrote a new function, called validateUserData. When you call this function, you pass it the memory address of the user data, and it does a quick but very thorough anal exam of the entire block of user data, looking for any deviation from the format that the user data is supposed to be in, according to my own, already-designed user data spec.
Of course, this function can’t find anything that might be wrong with the data, since it’s conceivable that the user data could be corrupted in a way that still looks like valid user data according to the spec, but it’s not too likely that will happen.
If validateUserData decides that everything is OK, it returns an empty string (i.e. no error message). But if it finds anything wrong with the user data, it immediately returns with a descriptive message. This message might not mean anything to a user (e.g. “Date list has negative length”), but to the developer (me) it’s very valuable information.
Then, I changed the app so that any time it does anything with the user data, it first makes a copy of the user data block, performs its change on the copy, then validates the new user data with validateUserData. If the new data validates, the old data is discarded, and the app continues normally. But if the data doesn’t validate, then the new data is discarded, the old data is kept, and the app pops up an alert saying that the action couldn’t be performed because it was going to cause corruption of user data. And the app displays the descriptive message returned by validateUserData.
Once I installed this feature into the app, I quickly discovered the cause of the above-described bug, and found a couple more bugs as well. Fixing these bugs was a breeze, because I knew exactly what action first corrupted the user data, and I had a description of exactly what was wrong with the structure of the user data.
And best of all: When I released the app to users, I did so with supreme confidence in the stability of the app because (a) validateUserData had given me a strong feeling that no obscure bugs were still lurking undetected, and (b) validateUserData promises to both protect users from data loss if they do run into an obscure bug, and give them the ability to report the bug in a way that will be extremely useful to my attempts to find and fix it.

Update 2012.03.02 — “iOS” added to title
