NSKeyedUnarchiver and Swift

Too many years using Objective-C (or any another language really) will give you a sixth sense. Let me show you an example: something in the back of your head should shout at the following code.

NSString *string = [self stringOrNil];
NSURL *url = [NSURL URLWithString:string];

If you don’t see the problem don’t give up, you were not bitten enough times. The problem is that +[NSURL URLWithString:] will throw an exception if URLString is nil.
We can avoid this by checking if URLString is nil.

NSString *string = [self stringOrNil];
NSURL *url = string ?[NSURL URLWithString:string] : nil;

Perfect. That’s what I do every project. I must keep in mind that +[NSURL URLWithString:] does that. The compiler won’t force you to implement a try/catch block like in Java, so you must have this present while coding. That sucks.

Now let’s consider the case of NSKeyedUnarchiver:

NSString *filePath = [self filePath]; //this is always a valid file path
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath]

Again, you might recognise the same problem as before. +[NSKeyedUnarchiver unarchiveObjectWithFile:filePath] will throw an exception if the file doesn’t contain a valid archive. But how can we test that in order to avoid the exception?

You can’t. You shouldn’t. This is exactly why exceptions exist. To let you try something that you can’t guarantee will work, and catch a problem in the exceptional situation of one occurring. Deal with it.

NSString *filePath = [self filePath]; //this is always a valid file path
id object = nil;
@try {
    object = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
} @catch (NSException *exception) {}

That’s one way of doing it, if you don’t want to deal with the error. But what about Swift? Exceptions didn’t make it in time for 1.0. If you decide to use NSKeyedUnarchiver.unarchiveObjectWithFile(_:) there’s nothing you can do in pure Swift to make sure your app won’t crash. At least that I know off.

I came up with a simple solution for this. Create a simple Objective-C category that provides the exception handling I need.

@implementation NSKeyedUnarchiver (SwiftUtilities)

+ (id)su_unarchiveObjectWithFilePath:(NSString *)filePath {
    id object = nil;
    @try {
        object = [self unarchiveObjectWithFile:filePath];
    }
    @catch (NSException *exception) {
        object = nil;
    }

    return object;
}

@end

Then, after adding that to the Bridging Header, I call it in Swift.

var object : AnyObject? = NSKeyedUnarchiver.su_unarchiveObjectWithFilePath(filePath)

This solution will still work if exceptions are introduced in Swift. And it’s so simple it shouldn’t require any changes for years to come. At least while Objective-C and Swift together are supported. If you know of a better way to do this I would be thrilled to hear from you via Twitter.

 
34
Kudos
 
34
Kudos

Now read this

WatchKit Day 2

Did I mention that WatchKit was limited? So far so good, Xcode’s Beta is crashing on an acceptable rate, Swift is starting to make more sense (yes, I’m also starting with that) and I had the opportunity to say my first “no, you can’t do... Continue →