Swift 2: Catching NSException

I'm working with an Objective-C library that throws NSExceptions for certain conditions (network or service unavailable).


Swift 2 will catch anything that is of type ErrorType, which NSException is not, so I've extended it to be of ErrorType:


extension NSException : ErrorType {
    public var _domain: String { return "some.domain" }
    public var _code: Int { return 0 }
}



I'm running the following code:


do {
    print("Pinging service")
    let response = try service.ping()
    print("Ping respones: \(response)")
} catch {
    print("Encountered error: \(error)")
}


It crashes during the ping()


Any idaes?

Replies

Swift error handling has nothing to do with exceptions. Instead, it is based on the return of an error parameter, just like errors in Obj-C, but with syntax support to hide the error parameter, which includes 'try' and 'catch' statements whose resemblance to exception handling is un-semantic.


You're probably going to have to write an Obj-C bridge that turns the exceptions into error returns (for exceptions thrown synchronously) or completion blocks (for exceptions thrown asynchonously).

It is implemented at machine-code level as an extra parameter (which for some reason is after another extra parameter that is not set in the caller and not used by the callee), at least in x86-64, but it could be viewed as a second return value. The implementation in machine-code can differ very much from high-level semantics, so that is not a reason to view it as a parameter.


For example, gcc on Sparc (I guess that it does the same thing as the Sun C compiler) generates call-by-value struct arguments as call-by-reference with copy-on-write.


The do-try-catch is exception handling, so in what way is it un-semantic? Or do you mean Objective-C exception handling?

No do-try-catch in Swift is NOT exception handling. It is just syntactic sugar for NSError.

It is implemented at machine-code level as an extra parameter […] The implementation in machine-code can differ very much from high-level semantics, so that is not a reason to view it as a parameter.


It's currently compatible with Obj-C, which means that an @objc method (explicitly or implicitly) it must behave exactly as if there were an extra parameter.


The do-try-catch is exception handling, so in what way is it un-semantic?


I meant that try, catch and throw are just keywords, and attempting to reason from what they mean in other languages is going to lead to incorrect conclusions.

Yes, a function is compatible with Objective-C, if you declare it as @objc, but not if it uses the Swift calling convention. The Swift calling convention could have been to return the error in a register, and you should not care as a programmer, but I think it is useful for the programmer to think of it as a return value, and not a parameter. The reason it is a parameter in Objective-C is because the usual C way of handling multiple return values is through pointer arguments. If you want to think of it as a parameter, fine, but be aware that it is just an emulation of a return value.


If you look at Java checked exceptions, would you say that they are exceptions? If so, what is the difference that makes the Java ones exceptions, and the Swift ones not?

Yes, and "return" is just syntactic sugar for "mov rax, returnvalue". But of course, it isn't, since that depends on the architecture, the return value type, the OS conventions, and so on. It also provides a type-checking function. In the same way, do-try-catch enforces things that NSError** arguments in Objective-C doesn't.

You make a reasonable point, but I think there is value in making a distinction because there is already an exception handling mechanism in our (Xcode) ecosystem, perhaps two, if you count the Obj-C and C++ exception mechanisms separately, and Swift is unaware of both of them.


I think there are three key differences between Swift error handling and exceptions in the other languages:


1. In Swift, 'throw' is a transfer of control to the calling function, similar a 'return'. Elsewhere, it's a transfer of control to somewhere else in the stack.


2. In the other languages, a function that isn't exception-aware can have a transfer of control across it (from below it to above it). In Swift, a non-exception-aware function blocks any error handling control transfer.


3. Swift has no system (program failure) exceptions, such as "invalid argument" or "nil pointer" that are fed into the error handling mechanism. Those sorts of failures immediately and correctly cause a crash. Actual errors appear only by API contract between a caller and a callee.


These distinctions are subtle, but they're non-trivial. If you don't see any signficant difference, then OK, but then you're also committed to calling Obj-C's '(NSError**) outError' pattern "exception handling", just with really verbose syntax.

Yes, what you say is true, but it is also true for Java checked exceptions, which is the "static typing" way of doing exceptions in Java. I'm not too fond of static typing myself, but the Swift team clearly is, because what I see in Swift is static typing, more static typing, and even more static typing, so it fits very nicely with the Swift model.


To quote from docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html:


"If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception."


Which is essentially the situation in Swift.


This post is not an endorsement of Java, but I think it is useful to look at it, since it provides a similar exception model, and one that many people are familiar with.

Hey,


You may find interesting the approach followed here... It's pretty match a code in Objective-C which handles the `NSException`


here you can find the code, pretty straightforward implementation:

https://github.com/williamFalcon/SwiftTryCatch


Hope this helps.
Adrian.