Archive for April, 2007

Backing up your app’s support files

Sunday, April 22nd, 2007

I’m not only the co-President of the Vienna Club for Men, Women, and Others: I’m also a client. Before I became a Vienna developer, I was a user. During development and debugging of the app, I don’t want to corrupt my user data, e.g., database and preferences, so I back them up before I do anything dangerous. Believe me, anytime I launch Xcode, it’s dangerous.

A long time ago I created a set of Automator actions to back up the files and restore them afterward. Yes, I’m the one of the five or so people in the world who actually use Automator. (Otto who? Auto Parts?) However, after I bought a laptop — or portable computer not for use on your lap under any circumstances, on penalty of burnt naughty bits, as they say — and I started using a FileVault account, I discovered that Otto and FileVault don’t play well together. (They can’t agree on who has to be Samwise.) Thus, I needed a new method of backing up and restoring my Vienna files.

Presto Change-o, Hocus Pocus, I created a custom Xcode project to do the job. At first I was just going to provide it to Vienna developers, but then I decided that I could (semi-)easily make it configurable to handle the files of other apps, since application support files tend to reside in standard locations. Later, greed got the better of me, and I decided to charge $50 a head for my creation. Finally, sanity got the better of me, and I’m making the source available for download. You can still send me $50 if you like, $100 if you have two heads.

The project is called BackupAppFiles, which is pretty boring, I admit, but hey, my parents named me Jeff Johnson. Anyway, BackupAppFiles comes configured to handle Vienna support files. To handle the files of a different app, just change the string constants in BAFConstants.m to match the file names. You can compile different versions to handle different sets of files on the same computer, but you’ll need to change the PRODUCT_NAME build setting for each version, because the location of BAF’s own support files depends on that setting.

The standard disclaimers apply once again. I shall not cause harm to any vehicle nor the personal contents thereof, etc. Please back up your files before using BackupAppFiles. (Yes, I recognize the irony there. No need to point it out, really.) I take no responsibility if BackupAppFiles erases your hard drive or makes your head explode, though I will refund your $50 in the latter case. (It happens sometimes. People just explode. Natural causes.) Let me know if you have any questions, suggestions, or complaints. About BackupAppFiles, that is. Or a plate of shrimp. I’ll also take bug reports and patches, just no feature requests. Now let’s go get sushi.

The cat’s in the bag: Vienna 2.1.2

Saturday, April 7th, 2007

I’ve decided to adopt Apple’s practice of naming software releases after cats. Therefore, today I’m announcing Vienna 2.1.2 Sylvester. The release notes are here, and you can download it there. Please post bug reports and feature requests yonder. It’s basically just a minor, bug fix release to tide you over until Vienna 2.2 Morris. Sufferin’ succotash! Since Vienna is open source, we’ve had to write convoluted, unreadable code in order to hide the secret new features in Morris. Not for any other reason.

That’s all, folks!

Invoking errors

Sunday, April 1st, 2007

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.