Handling crashes and NSAssert gracefully in a Production App

NSAssert? What? Why? (skip if you’re familiar with this)

During development, it’s standard “best practice” to us NSAssert liberally, alerting you early if you have unexpected bugs in your code, for instance:

-(NSString*) processItem:(int) index
{
	NSAssert( index < [myArray count], @"Index is bigger than number of items in the myArray array" );
	...
	... // rest of the method goes here
}

Unit Tests are an even better approach, but there are situations – e.g. complex algorithms – where you want to perform sanity-checks in the middle of code.

NSAssert in Xcode (Apple’s default setup for new projects)

Apple has hooked up NSAssert so that in Debug builds (by default: anything on the simulator or on the device using Development mode) it stops your app, and immediately tells you what’s gone wrong.

When you do a Release build (by default: anything you send out as Ad-Hoc, or to the Apple App Store), Apple automatically replaces your NSAssert calls with blank lines – nothing happens. Otherwise your app would crash in many situations where it’s probably OK to keep running.

But that means you lose this info that could be very valuable in discovering if your app has stopped working, and how/why – e.g. if Apple changed something in an iOS update that now breaks your app.

Ideally, we want to do something different for Release builds.

NSAssertionHandler to the rescue

In older platforms, you often had to manually do a #define to change the assert() function to something different, in different builds. That was midly annoying – complex macros are slightly harder to maintain than straight code – and not very configurable.

Fortunately, Apple uses an OOP approach via NSAssertionHandler. You can implement *multiple* subclasses of NSAssertionHandler, and switch between them at runtime.

First, create a subclass of NSAssertionHandler:

@implementation AssertionHandlerLogAll

-(void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ...
{
	NSLog(@"[%@] Assertion failure: FUNCTION = (%@) in file = (%@) lineNumber = %i", [self class], functionName, fileName, line );
}

-(void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ...
{
	NSLog(@"[%@] Assertion failure: METHOD = (%@) for object = (%@) in file = (%@) lineNumber = %i", [self class], NSStringFromSelector(selector), object, fileName, line );
}

@end

…then, in your AppDelegate class (whichever class implements “NSObject ” ):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	// Override point for customization after application launch.
	NSLog(@"[%@] Setting a custom assertion handler", [self class] );
	NSAssertionHandler* customAssertionHandler = [[AssertionHandlerLogAll alloc] init];
	[[[NSThread currentThread] threadDictionary] setValue:customAssertionHandler forKey:NSAssertionHandlerKey];
	...
	// NB: your windowing code goes here - e.g. self.window.rootViewController = self.viewController;
}

Assertions – upload to Flurry, perhaps?

But why limit yourself to just one assertion handler?

If you’re using Flurry (or Google Analytics etc), why not automatically log + upload each assertion to Flurry, and have them show up in your Dashboard? That way, you can see if ANY assertions are firing – but also get a count of:

  1. how many
  2. which hardware (is this only happening on an iPhone 3GS, for instance?)
  3. which iOS version (perhaps it’s due to a bug in iOS v 4.2, but not in iOS 4.3?)

Something like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	// Override point for customization after application launch.
	/** start Flurry  */
	[FlurryAPI startSession:@"whatever your private Flurry Key is"];
	
	NSLog(@"[%@] Setting a custom assertion handler", [self class] );
	NSAssertionHandler* customAssertionHandler = [[AssertionHandlerSendToFlurry alloc] init];
	[[[NSThread currentThread] threadDictionary] setValue:customAssertionHandler forKey:NSAssertionHandlerKey];
	...
	// NB: your windowing code goes here - e.g. self.window.rootViewController = self.viewController;
}

Archiving old GIT projects on Beanstalk or Github

EDIT: re-written to make it much clearer what I’m trying to achieve, and why.

Why Archive? Why not Archive?

The pricing model for hosted git has settled down to:

  1. Pay per month
  2. …for an upper limit on the number of active repositories
  3. ……measured by simultaneously limiting the number of “projects” (separate git repositories) and “people” (user accounts that are allowed to access projects)

Their aim seems to be: charge for peak concurrent usage, rather than for total historical usage.

For instance: if you have 20 user accounts allowed, and you use all of them, then delete 10, you can create 10 new ones. The vendor will NOT delete all the history of the deleted accounts – they just won’t allow you to login as those users any more.

This probably is setup that way to make sure:

  1. their revenue scales with their costs – these days, with scalable hardware costs, that’s straightforward.
  2. their prices scale with the budget-size of their customers

Some SAAS vendors selling at the same kind of price level / model allow this same “disabling” function on whole projects, not just on people. That enables you to e.g.:

  1. Work on 1 new project day-to-day
  2. Have 10 “old” projects that are no longer active (previously shipped)
  3. Reserve the right to temporariliy activate any ONE of the 10 – e.g. to enact a quick bugfix / maintenance release
  4. …while only paying for “2 simultaneous projects”

This works fine – the resource usage is closer to that of a company with only 2 active projects than it is to a company with 12 active projects, and the price you’re able to pay is too.

Unfortunately, at the moment neither Beanstalk nor Github offer this – although they’re both great git-hosting services.

Archive options

In practice, we need to support these use-cases:

  1. Old project that MIGHT still be around in a local repository needs small tweaks and a quick re-launch: typically only 1-3 files need to be edited.
  2. Very old project that definitely isn’t in a local repository any more: ditto
  3. New project needs to solve a problem that was previously solved in an old project: need read ONLY access to the full project to revise “how we fixed this last time”

Before signing up to a git host, I asked each of them how they coped with these use-cases (in some detail); each company responded with, essentially:

We don’t have any support for this. Best we can sugest: copy all old projects into a single repository

Archiving git projects via copy/paste

What happens when you try to do this?

Well, for a start, you can’t “just copy” the contents of one git project into another.

Git uses hidden directories to manage its source control, and is hugely reliant upon them. This causes a handful of problems relating to “is file X still file X?”, one of which is this one of copying between repositories.

Naively, you’d try to do this:

  1. Create a global “archive” repository where you will move *all* old projects (this was initially recommended to us by Beanstalk/Github)
  2. PULL the latest copy of the git repos you want to archive
  3. MOVE the root directory into your “archive” repository
  4. PUSH the “archive” repository to the git-host

In practice, what happens is:

  1. Every modern Operating System moves the .git hidden directory too
  2. …so you fail to do a checkin (and at this point: a lot of the current Git GUI clients will break in interesting ways; it’s a good test for a new client if you’re considering buying one)
  3. …so you fail to do the PUSH

Copy/pasting by discarding the history

EDIT: if git-archive works for you, I’d use that instead. Everyone I know who’s used it has had at least some problems, so I’m leaving this section for now – but scroll down to the next section and check git-archive too.

On anything unix-based (i.e. linux + OS X) the simple path is to use the command-line (or “terminal” as OS X calls it)

  1. “cd [the root directory of your project you want to archive]”
  2. “cd ..”
  3. “cp -R [the directory of project to archive] [the root directory of the "archive" repository]/[name of the project you want to archive]”
  4. “cd [the root directory of the "archive" repository]/[name of the project you want to archive]”
  5. “chmod -R u+rw .git” (otherwise you’ll have to say “yes” to every individual file delete)
  6. “alias rm=rm” (otherwise you’ll have to say “yes” to every individual file delete)
  7. “rm -R .git”

…then, in your git-client:

  1. COMMIT the “archive” repository

…then, in your git-host service:

  1. DELETE the old project

The key points here:

  1. You’re copying the repository, not moving it – so the original is unaffected (if you don’t have to delete it yet, you might as well leave it intact)
  2. You’re removing all git status from the files: it becomes a virgin archive
  3. DISADVANTAGE: you’re throwing away (deleting) all history for the old project.

Using Git Archive

Git archive isn’t perfect (archiving is a complex task, and from what I’ve seen git archive doesn’t cover every use-case). I’ve met a couple of people who’ve tried it and given up (e.g. because it didn’t support submodules), but it might work for you: Worth a try.

Other alternatives

In furture, I’m going to try out some of the many other alternatives listed on the SO page linked above.

Xcode4: A script that creates / adds files to your project

This ought to be easy: it is the single most common function that a build system has to do.

So it’s rather depressing that Apple doesn’t support it. Every other build system (and IDE) I’ve ever used supports it out of the box. Apple for some unknown reason has designed Xcode4 to make this difficult. Strange.

The ultimate “fix” is just one line of code – but it’s a line of code that many people are afraid to write, because it seems like it would be fragile, and feels like it MUST be wrong. Most of this post is explaining why, in fact, it’s correct – and walking you through the other things we attempted before settling on this fix.

How you’d expect it to work

We’d expect: Step 1 – Add a “Run Script” phase

Aplpe has a simple process for adding scripts. Their script-management is very weak (it’s not been updated to modern standards in a long time), so you only have one option:

  1. Select the project itself, inside the project (it’s the thing with the blue icon)
  2. In the main window, select the target you’re building
  3. Click the Build Phases tab
  4. Hidden in the extreme bottom right corner of the screen (by a bad UI designer) is a button “Add Build Phase”, that lets you add a “Run Script” phas

OK, done. Wait … it’s asking us for “input files” and “output files”. What does that mean?

Well, whatever you were expecting is probably wrong: remember, this is a very weak build-system. It’s actually asking you:

  1. Every file you intend to read from, please tell me in advance, so I can cache your output if those files haven’t changed
  2. Every file you intend to write to, please tell me in advance, so I can cache the BUILD if your output hasn’t changed

(all modern build systems do the second feature automatically, and good ones have also been doing the first one automatically for over a decade)

Fine, so you fill those in. NB: even though you should be able to select input files from a Finder interface, Apple has disabled the Finder on this GUI, so you have to type them fully by hand.

What happens: The script runs, but the files vanish

If you use some debugging in your script, you can prove its run, by looking at the build log.

But the output files are NOT included in the build (even though we told Apple each file, by name).

Hmm. Well, maybe we need to use the only other option Apple provides us:

We’d expect: Step 2 – Add a “Copy Files” phase?

Actually, this should be automatic; we just told Xcode exactly what files we were creating, and where we were creating them.

But some badly-written build-systems are so dumb that you have to tell them what you’ve already told them. So, when step 1 above doesn’t work, we try step 2.

  1. Add a “Copy files” phase immediately after the “Run Script” phase
  2. Type in the name(s) of the file(s) generated in the “Run Script” phase
  3. …FAIL! Xcode4 is hardcoded to prevent you doing this

Yes, really. The “Add” button is grayed-out if you attempt to do this. Apple doesn’t want you to do it.

There is literally no way to take the output of a script and include it in your app.

Solving the problem with Xcode4

Instead, you have to write *into your script* the parts of Apple’s build system that they have barred you from accessing. Obviously, Apple is using “copy the output of a script” to take your compiled files and put the output into the app – but they won’t let you do that.

Here’s where it gets tricky: Apple’s documentation for Xcode4 is so misleading that I’d call it “incorrect”: it tells you to use the Copy Files phase described above (which we already know is impossible), and if you go digging in their build-system docs, it gives you various other places it suggests you manually copy your output files to.

But they don’t work, either because they’re missing key elements, or because Xcode4 ignores their content when making the build.

Pre-Solution: edit your attempts at Steps 1 and 2 above

Firstly, since Apple is ignoring the “output files” that we so carefully specified, and their docs say they will just re-run the script every time if the output files are blank … remove all your “output files” and “input files”. It’s the only safe way forwards – otherwise you’ll have to hand-maintain this error-prone dialog box forever.

Secondly, delete the Copy Files phase – Apple won’t let you use it. Give up, and move on.

Solution: Manually “insert” files into the final MyApplication.app file

I tried everything, and scoured the Apple docs, the Apple developer guides, StackOverflow, and Google. I found two things that worked: an easy one that requires some typing, and an extremely long-winded and difficult one that hacks Xcode’s GUI to force it to do what it was supposed to in the first place.

Too much effort, too much to go wrong – and too hard to maintain. Instead, I went for the quick and easy method. Fortunately, there’s a pair of poorly documented Apple build-variables that together makes this easy and (almost) idiot-proof. At least, they do if you know they exist, and once you’ve guessed what they actually do (as opposed to what the docs suggest).

Add the following to the very end of your script (or get your tool:

cp ${DERIVED_FILE_DIR}/[YOUR OUTPUT FILES] ${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}

…or just write your script so that it writes output directly to:

${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}

If you use script debugging, you’ll find that this deposits things directly inside the MyApplication.app file – and it works fine.