0

In my code I'm deserialising some XML using the SWXMLHash library. Its .value() method has the throws keyword in its declaration, as do any custom deserialise functions.

I have the following line of code:

let myValue : UInt8 = try? xml["Root"]["ValueNode"].value()

Since the library doesn't include a deserialiser for UInt8, I've defined my own:

extension UInt8: XMLElementDeserializable {
    public static func deserialize(_ element: XMLElement) throws -> UInt8 {
        return UInt8(element.text)!
    }
}

This works when the node has a value. However, when the node doesn't exist or is nil, an error occurs on the following line:

return UInt8(element.text)!  // Fatal error: Unexpectedly found nil while unwrapping an Optional value

This is supposed to happen, obviously. What I don't understand is why this error is not being caught by my try? statement and returning nil instead of throwing that error.

Can anyone help?

Hamish
  • 78,605
  • 19
  • 187
  • 280
Extragorey
  • 1,654
  • 16
  • 30
  • You can't deal with fatal errors using `throws` and `try`, it's fatal. There are proper ways built into Swift to deal with optionals. – rmaddy Dec 11 '17 at 23:07
  • This has been covered quite a few times before: https://stackoverflow.com/a/37222884/2976878, https://stackoverflow.com/a/38738365/2976878, https://stackoverflow.com/q/46866311/2976878, https://stackoverflow.com/q/31335023/2976878 & https://stackoverflow.com/q/43009744/2976878 to name a few. – Hamish Dec 11 '17 at 23:20

2 Answers2

1
return UInt8(element.text)!

There's no try in this line. Therefore, no errors are going to be thrown here. If UInt8 can't convert the string it's given, it just returns nil. And then, of course, your ! turns that nil into a crash.

Instead of that, do something like this instead:

guard let retVal = UInt8(element.text) else { throw SomeError }
return retVal

In general: When there's a way to do something using !, and another way to do the same thing without using !, go for the second one unless you've got a really good reason.

Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
1

Not all errors can be caught in Swift. If you mark a function using the throws keyword it indicates that the function might throw a recoverable error. However, your custom implementation doesn't actually throw any errors. Only errors thrown from functions marked with the throws keyword and thrown using the code throw Error can be caught by a do-catch block.

try? is a way to convert a throwable function's return value to an optional. If the function would throw an error, the value after the try? will be nil, otherwise it will be an optional value.

When you use the !, you specifically tell the compiler that you know what you are doing and if the operation on which you used the ! fails, your app shouldn't fail gracefully.

You'll need to change your deserialize method to handle the optional unwrapping gracefully or throw and error.

extension UInt8: XMLElementDeserializable {
    public static func deserialize(_ element: XMLElement) throws -> UInt8 {
        if let val = UInt8(element.text) {
            return val
        } else {
            throw NSError(domain: "Couldn't deserialize value to UInt8", code: 1)
        }
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116