Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Catch Pattern that binds the error to a variable

Using Swift 4.2 and XCode 10

In Swift 4.2, DecodingError is an enumeration. There are (currently) four different cases. I can catch each case separately, and bind variables that I can use to log the error as in the following code ...

do {
    let model = try jsonDecoder.decode(BattleShip.self, from: jsonData!)
    print(model)
} catch DecodingError.dataCorrupted(let context) {
    print(context.debugDescription)
} catch DecodingError.keyNotFound(let key, let context) {
    print("\(key.stringValue) was not found, \(context.debugDescription)")
} catch DecodingError.typeMismatch(let type, let context) {
    print("\(type) was expected, \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
    print("no value was found for \(type), \(context.debugDescription)")
} catch {
    print("I know not this error")
}

But this is a lot of code to put everywhere I could encounter a decoding error. And, if my do{} block has multiple calls that throw, I may need to handle the errors that those methods call differently. The pattern I am trying to implement looks like this ... where decodingError(error) has all of the messy code above

do {
    let object1 = try decoder.decode(SomeClass.self, from: someData)
    try object2.methodThatThrowsSomeOtherError()
} catch <all decoding errors> {      // this is invalid pseudocode
    MyCentralLogger.log.decodingError(error)
} catch let nonDecodingError {
    MyCentralLogger.log.error(nonDecodingError)
}

I can have a catch pattern like this that seems to satisfy all of the enumeration cases (at least it compiles)

} catch is DecodingError {

but the compiler doesn't seem to autobind the 'error' variable, and I don't see any option like

} catch let decodingError is DecodingError {  // THIS IS NOT VALID

If I just catch all errors, I can easily have a switch in a central method that separates the different decoding error cases appropriately. But I want to be able to avoid sending non-decoding errors into that switch. I can also separate my do{} blocks so that I'm only performing decoding steps in it, but this also makes code messy, particularly if you are decoding multiple messages interspersed with other actions.

Suggestions? Thanks all!

like image 953
escapedcanadian Avatar asked Jan 02 '19 17:01

escapedcanadian


People also ask

What happens if you don’t catch errors in Swift?

This indicates that the method may raise an error and as a best practice, you should gracefully handle the error and show an appropriate error message to the user. If you don’t catch the error, then your app will crash! Here’s an example of the Swift do-try-catch syntax:

What does try catch mean in Swift?

Swift Try Catch When you’re coding, you’ll see methods that have the throws keyword. This indicates that the method may raise an error and as a best practice, you should gracefully handle the error and show an appropriate error message to the user. If you don’t catch the error, then your app will crash!

What is nserror in Swift?

Error handling in Swift interoperates with error handling patterns that use the NSError class in Cocoa and Objective-C. For more information about this class, see Handling Cocoa Errors in Swift. In Swift, errors are represented by values of types that conform to the Error protocol.

What is an expression pattern in Swift?

An expression pattern represents the value of an expression. Expression patterns appear only in switch statement case labels. The expression represented by the expression pattern is compared with the value of an input expression using the Swift standard library ~= operator. The matches succeeds if the ~= operator returns true.


2 Answers

The syntax used in a catch line is exactly the same pattern syntax used in the case of a switch. If you know how to write a case you know how to write a catch.

So, for example, you complain:

} catch let decodingError is DecodingError {  // THIS IS NOT VALID

Right. But this is valid:

} catch let decodingError as DecodingError { 

Oh what a difference one letter makes.

like image 186
matt Avatar answered Oct 19 '22 04:10

matt


This is still a lot more code than desired, but maybe a little bit cleaner:

} catch DecodingError.keyNotFound(_, let context),
        DecodingError.valueNotFound(_, let context),
        DecodingError.typeMismatch(_, let context),
        DecodingError.dataCorrupted(let context) {
    print(context.debugDescription)
    MyCentralLogger.log.decodingError(context.underlyingError)
} catch {
    print(error.localizedDescription)
    MyCentralLogger.log.error(error)
}
like image 1
Juan de la Torre Avatar answered Oct 19 '22 02:10

Juan de la Torre