5

The docs and popular blogs suggest Swift error handling be done with do-catch and to handle an ErrorType enum or an NSError instance.

Are ErrorType enum and NSError instances mutually exclusive in a try catch block? If not, how do you implement a function that throws both?

I have associated an NSError instance to an enum like so, which seems to work, but is this the de facto way of returning detailed error information?

enum Length : ErrorType {
    case NotLongEnough(NSError)
    case TooLong(NSError)
}

func myFunction() throws {
    throw Length.NotLongEnough(NSError(domain: "domain", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey: "Not long enough mate"]))
}

do {
    try myFunction()
} catch Length.NotLongEnough(let error) {
    print("\(error)")
}

This example shows how ErrorType can be cast to NSError.

do {
    let str = try NSString(contentsOfFile: "Foo.bar",
                           encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
    print(error.localizedDescription)
}

I can't find an error enum that conforms to ErrorType for NSString so should we assume it will be an NSError instance ? Granted we could run the code to be sure, but surely the docs should let us know. (I appreciate I might have mis-read the docs)

2 Answers2

1

Any ErrorType can be successfully cast to an NSError, which means that if you want to handle ErrorType as a priority, or specific ErrorType conformant Swift types, you should have those cases before a case where you cast to NSError (which, again, will succeed for all ErrorType conform ant types).

I'm not sure if there are any canonical opinions expressed by Apple at this stage on how to deal with this duality right now, but personally I try to stick to ErrorType conforming Swift types for my own errors and only involve casting to NSError when I want to make conditional behaviour based on some Cocoa or 3rd party NSError based code where the domain & code combo is somehow meaningful to my logic (for instance if specific errors can be filtered out from either logging or from responses).

Problems with the existing facilities in Swift for error handling arise mostly from not being able to tell what kind of errors are thrown by a method, combined with ErrorType lacking some key contextual information included in NSError that gives extra bespoke work every time you want to present ErrorTypes in UI to the user somehow. Unlike NSError, ErrorType does not have a way to pass UI intended information such as a "reason", or "recovery suggestion", or indeed a "description". It looks like this also will not get addressed yet for Swift 3 (there has been some recent discussion on this on the Swift development mailing list).

Regarding "I can't find an error enum that conforms to ErrorType for NSString" I'm really not sure I understood this phrase correctly or whether it is worded correctly overall, but maybe the following is relevant:

I personally follow the convention of making my ErrorType implementations CustomStringConvertible and using the description property to describe a human (UI intended) readable description of the error. This is by no means perfect and especially on OSX where NSResponder nicely enough gives you the presentError method which makes a very nice and clear error dialog if you fill in the description & recovery suggestion info.

mz2
  • 4,672
  • 1
  • 27
  • 47
  • Thanks for the detailed response, it's much appreciated. I guess we could typealias our own 'userInfo' Dictionary or a tuple containing information about the error and pass that into an associated enum for ErrorType. Would have to do it for each listed case though. Will keep an eye on the mailing list for now. Thanks again. –  May 06 '16 at 16:13
0

NSError class adopts ErrorType interface and any ErrorType-conformant class can be casted to NSError. These features are described here in the docs.

You can safely stick to ErrorType, especially if you're planning to interoperate with Swift only.

enum CommonError: ErrorType {
    case InternalInconsistency(String)
}

func boom() throws {
    throw CommonError.InternalInconsistency("Boom!")
}

do {
    try boom()
} catch {
    print(error) // InternalInconsistency("Boom!")
    print(error as NSError) // Error Domain=CommonError Code=0 "(null)"
}

do {
    try boom()
} catch let CommonError.InternalInconsistency(msg) {
    print("Error: \(msg)") // Error: Boom!
}
werediver
  • 4,667
  • 1
  • 29
  • 49