Invoking errors

If I had to name my specialty as a developer, it would be error code. I’ve reached a point in my career where I can invoke errors almost instinctively. This post is about NSErrors, however, which differ from regular errors in one crucial respect: the more NSErrors in your app, the better. Yes, even when Apple makes errors, they’re appealing. This post is also about NSInvocations, which sound arcane, and they are. I use them, but I’m afraid of them … kind of like microwave ovens. I have no idea what’s going on inside, but they make good Cocoa. (Cat’s breath, charm of sleep and bath, thy omen of scratching.)

There will come a time, maybe not today, maybe not tomorrow, maybe not soon, maybe not for the rest of your life, but someday anyway, when you’ll want to use an NSError as an argument to an NSInvocation. Or maybe never, but you’ll certainly want to use a pointer to a pointer to an NSError. That’s an error’s cousin, once removed; they can marry in selected states. The standard pattern for handling errors with Cocoa methods is to directly return a BOOL indicating success or failure and to return ‘by reference’ (look that up in your Funk & Wagnalls) an NSError that provides additional information about the failure, e.g., You’re a loser, and who does your hair, Floyd the barber? If you call the method by name — heyJoe — you can just pass the address (&) of your NSError object as an argument to the method. What if you want the method to vary at runtime, though?

Suppose that your app needs to perform a series of disparate tasks, and each task requires error checking afterward to determine whether the series should continue. Using conventional methods, your code could become a winding mess. All roads lead to Rome, just not in a straight line. The more pleasant alternative, in my opinion, is to wrap up everything into NSInvocations at the beginning to store in an NSArray for iteration. And rather than having a monstrous error checking method with endless if else clauses, you could have an aptly named error checker corresponding to an individual task, just as in key-value coding you have an aptly named setter corresponding to the getter. For example, we have a sample error checking method:


-(BOOL) didSucceedAtJoe:(id)result error:(NSError **)error {
	BOOL didSucceed = result != nil;
	if (!didSucceed && error) {
		*error = [NSError errorWithDomain:@"MyErrorDomain" code:69 userInfo:nil];
	}
	return didSucceed;
}
	

Such a method would be invoked by a generic error checker:


-(BOOL) didSucceedAtTask:(NSDictionary *)taskInfo {
	BOOL didSucceed = NO;
	NSError * error = nil;
	NSInvocation * invocation = [self invocationForDidSucceedAtTaskWithName:[taskInfo objectForKey:@"name"]];
	if (invocation) {
		id result = [taskInfo objectForKey:@"result"];
		[invocation setArgument:&result atIndex:2];
		NSError ** errorPointer = &error;
		[invocation setArgument:&errorPointer atIndex:3];
		[invocation invokeWithTarget:self];
		[invocation getReturnValue:&didSucceed];
	}
	NSLog(@"error:%@", error);
	return didSucceed;
}
	

It’s crucial to the invocation that you declare, and thus allocate memory for, both NSError * error and NSError ** errorPointer. It’s not sufficient simply to declare the latter, and you definitely can’t use a construction such as &(&error), because &error basically just gives a number, which has no address itself. Finally, we have the method for creating the invocation:


-(NSInvocation *) invocationForDidSucceedAtTaskWithName:(NSString *)name {
	NSInvocation * invocation = nil;
	if (name) {
		SEL selector = NSSelectorFromString([NSString stringWithFormat:@"didSucceedAt%@:error:", name]);
		if (selector) {
			if (ivarInvocation) {
				invocation = [[ivarInvocation retain] autorelease];
				[invocation setSelector:selector];
			} else {
				NSMethodSignature * signature = [self methodSignatureForSelector:selector];
				if (signature) {
					invocation = [NSInvocation invocationWithMethodSignature:signature];
					if (invocation) {
						ivarInvocation = [invocation retain];
						[invocation setSelector:selector];
					}
				}
			}
		}
	}
	return invocation;
}
	

The invocation is stored as an instance variable because the method signature depends only on the argument and return types, so it will remain the same for all of our selectors. Now our apps can invoke errors at a rate matched only by analysts, economists, and pundits.

On a personal note, I’d like to invite my faithful readers (all three, including myself) to my wedding today. Although the location is unusual — the Virgin Megastore in Orange County — it will nonetheless be a traditional ceremony. Pamela Anderson is signing.

One Response to “Invoking errors”

  1. [...] Jeff Johnson recently posted an entry about using NSError in Cocoa to dramatically improve error handling. I wholeheartedly agree with the idea, and in fact adding NSError “variants” during MPQKit’s refactor was one of my top priorities. [...]