Sunday, July 13, 2014

Swift and Cocoa Error Handling

I’ve not see much written about how to do real-world Cocoa error handling in Swift. That is, you’re calling Objective-C methods with NSError ** parameters and need to return an NSError back to your caller. This is very common with Cocoa.

NSError is unwieldy, especially when you must consider that the output parameter could be NULL. The way I handle this in Objective-C is to use a macro to reduce the amount of error handling code that’s visible:

NSError *e = nil;
NSString *string = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&e];
MJT_REQUIRE(string, e, error);
[string doSomething];

This leaves straight-line code, with no indentation, for the normal case. The macro, inspired by Apple’s AssertMacros.h, does nothing if its first parameter indicates success. Otherwise, it propagates the second parameter (the local error object) to the third parameter (the passed-in error pointer), but only if the latter isn’t NULL. It can also add some contextual information to error object, to track the source of the error. Lastly, it returns nil to indicate failure to the caller.

(I also have MJT_BOOL_REQUIRE(), which returns NO instead of nil, and MJT_REQUIRE_PARAMETER(param, error), for when a parameter can’t be nil. This is sometimes preferable to raising an exception with NSParameterAssert(), and it is otherwise very verbose to construct an appropriate NSError to return.)

Swift doesn’t have macros, so it’s not clear to me how this sort of pattern can be encapsulated. It looks like the code would be something like this:

var e : NSError?
let possibleString = NSString(contentsOfURL:url, encoding:NSUTF8StringEncoding, error:&e)
if possibleString == nil {
    if error {
        error.memory = addErrorContext(e, context)
    }
    return nil
}
let string = possibleString as NSString
string.doSomething()

I don’t like the way this code looks. I could probably write a helper function to make it something like:

var e : NSError?
let possibleString = NSString(contentsOfURL:url, encoding:NSUTF8StringEncoding, error:&e)
if failed(possibleString, e, error) {
    return nil
}
let string = possibleString as NSString
string.doSomething()

But I don’t see how to hide that if and return. And then there is the matter of having to create a second variable for the non-optional string if you don’t want to add ! after each use. I hope I’m missing something here, because it looks like Swift is making basic code more tedious without providing any real benefit.

13 Comments RSS · Twitter


Your second code block doesn't look right in many places.
There are missing commas (line 2), an invalid opening brace (line 2), an invalid colon (line 3).

If you could rewrite the post with correct code (and I don't care about "doSomething" not being implemented), you might get more discussion or help.

But you're right, error handling isn't quite what I'd expect from Swift, given that there are Optionals and tuples now. Methods like contentsOfURL should really return something like a tuple of (NSString?, error?).


First thing, I don't think you need that local error variable. Just pass the error parameter you were given. This way you don't have to copy.

NSString *string = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:error];
if (!string) {
    return addErrorContext(error);
}
[string doSomething];

Now in Swift:

let string = NSString(contentsOfURL:url, encoding:NSUTF8StringEncoding, error:error)
if string == nil {
    return addErrorContext(error, url)
}
NSString.doSomething()

As for not repeating the !, you can define the variable itself with a ! if you specify the type:

let string: NSString! = ...

@Pit Sorry about the syntax errors. I think those are fixed now.

Returning a tuple would make this a little cleaner, but you would still need to have an if and a return after every line that could return an error.

@Michel Yes, the local error variable is not necessary in this example. I included it because in real code I generally like to have it. This lets me inspect the reason for the failure, log the error if necessary, use it inside a higher-level error object, etc.

Thank you for reminding me about implicitly unwrapped optionals. I guess that’s what we’re meant to use here, although then the compiler doesn’t check that we handled the possibility of failure. So how is the type system helping us? And I’m typing more (let string: NSString! vs NSString *string), too.


Hey, you can get rid of if and return by using blocks.
Full example at the link. Note: it doesn't run, just a possible syntax example.

Also one should probably use generics to encapsulate an idea of transformation between different types, not just String.

func load(website:String, errorPointer:NSErrorPointer = nil) -> String? 
{ return Bound(initial:website, pointer: errorPointer)
    
    .swiftStyle {
        $0.isEmpty ? .Error(error:SomeError())
                   : .Success(value: "http://www." + $0 + ".com")
    }
    
    .objCStyle { (incoming, pointer) in 
        let url = NSURL(string: incoming)
        return NSString(contentsOfURL:url, encoding:NSUTF8StringEncoding, error:pointer)
    }
 
    .debugErrors { (error:NSError) in
        println(error) // or set breakpoint here
    }
    
    .cantFail { (inout value:String) in
        value += ""
    }
    
    .value()
}

@ilya It’s not clear to me how calling your load() function is any cleaner. And using blocks adds a level of indentation to what should be straight-line code.


Haven't had time to spelunk Swift yet, but ISTR it has sum types, in which case you want to be able to write write:

let stringOrError = NSString.stringWithContentsOfURL(url, encoding:NSUTF8StringEncoding)

The caller can then pattern match on the return value's class to determine which block of code to execute next: the one that processes the string or the one that reports the error.

As you say, lack of macros makes it difficult to apply such transformations yourself. Although given the choice between no macros and C "macros" (muck rows?), I think I'd still rather have the former than the latter.

Besides which, given Swift's stated goal of being a first-class Cocoa client, this seems like the sort of pattern-driven transformation that would be best baked into the language itself.

It's just a shame Distributed Objects' in/out annotations never became a standard notation in all method signatures, as that would've made it damn near trivial to generalize such transformations so that they could apply to all argument-based return values.

Whatever the Swift guys eventually do though, any user who thinks:

let (result, error) = whatever(...)
if (!result) {do stuff with error}

isn't the absolute worst of all options to be slapped with a big fat clue stick before they get any further. That sort of painful non-Swiftian anti-pattern that is going to drive everyone up the wall with its clunky lame and fail, giving Swift a false reputation for suckage when the real problem is Swift noobs (i.e. everyone right now) screwing up due to their own preconceptions, prejudices, and misunderstandings about how the language and its idioms actually work.


@has That would be the Swift way to do it, but it doesn’t (yet?) automatically transform the Objective-C APIs like that.

I like pattern matching, but I don’t see it as a good solution for handling errors for a sequence of calls that could fail. It seems even worse than using if (with early returns for errors) because you end up with many levels of indentation for the normal case. Have you seen code that does this and actually looks good?

I have done the tuple thing with PyObjC. Not a fan.


David Owens II writes about native Swift error handling. He implements custom enum methods to avoid pattern matching. This way still needs if and return.


The fundamental problems with NSError are that it mucks up your code and puts the error at the lowest level. All you could get out of the example above is that you failed to create a string with the contents of the URL. So? Were you trying to read a config file? A data file? A remote file? An XML stream? No way to tell. You have this error message that is virtually useless. Because Cocoa has broken exceptions, there is no way to associate an error with the operation you were trying to perform.

I'm going to have to come up with an alternate error handling strategy. I can't use exceptions and I'm not going to use NSError. How about some global structures that can keep track of major waypoints during the execution of your program. It can keep an NSError ** handy and you can pass that to any method. If the method returns false you can trigger your waypoint watcher which can log messages, alert the user, recover and try again, depending on how you've setup your waypoint.


@John The syntax for NSError is not great, but I don’t really understand your other criticisms. A good error will include the URL of the file that you were trying to read, why it failed, etc. And, as you pass the error up through your code, you can annotate it or wrap it to associate it with higher level operations. Seems to me that it would be more of a pain to do this with a global watcher, especially if threads/queues are involved. However, I do like the idea of having an error-handling policy object that can be configured or switched out at runtime.


That is exactly the problem with NSError. I don't want to have to weave low-level error handling all throughout the logic of my software. Any particular NSError only has meaning where it occurred and only for the programmer while debugging. It has no value for the user or programmer after the fact. Users need meaningful errors in the context of the operation they were attempting to perform. Programmers need a stack trace after the fact and to keep messy error handling code out of sight during development.

I would have preferred an elegant, supported error management architecture like exceptions. I like the waypoint idea. I am going to write that up.


[…] is great, but I still wish for the power of actual […]


[…] Swift and Cocoa Error Handling, JSErrorStackTrace, […]

Leave a Comment