Error Handling in Swift 3 - swift

I'm migrating my code over to Swift 3 and see a bunch of the same warnings with my do/try/catch blocks. I want to check if an assignment doesn't return nil and then print something out to the console if it doesn't work. The catch block says it "is unreachable because no errors are thrown in 'do' block". I would want to catch all errors with one catch block.
let xmlString: String?
do{
//Warning for line below: "no calls to throwing function occurs within 'try' expression
try xmlString = String(contentsOfURL: accessURL, encoding: String.Encoding.utf8)
var xmlDict = XMLDictionaryParser.sharedInstance().dictionary(with: xmlString)
if let models = xmlDict?["Cygnet"] {
self.cygnets = models as! NSArray
}
//Warning for line below: "catch block is unreachable because no errors are thrown in 'do' block
} catch {
print("error getting xml string")
}
How would I write a proper try catch block that would handle assignment errors?

One way you can do is throwing your own errors on finding nil.
With having this sort of your own error:
enum MyError: Error {
case FoundNil(String)
}
You can write something like this:
do{
let xmlString = try String(contentsOf: accessURL, encoding: String.Encoding.utf8)
guard let xmlDict = XMLDictionaryParser.sharedInstance().dictionary(with: xmlString) else {
throw MyError.FoundNil("xmlDict")
}
guard let models = xmlDict["Cygnet"] as? NSArray else {
throw MyError.FoundNil("models")
}
self.cygnets = models
} catch {
print("error getting xml string: \(error)")
}

Related

Why try/catch block doesn't cover all the runtime exceptions?

What I try to do is
...
let path: URL? = URL(string: "")
do {
let data = try Data(contentsOf: path!)
}
catch {
print("ERROR: \(error)")
}
...
I know that this code won't work, but what I expect is to catch the error in the catch block instead I get a runtime exception and crash.
try this
..
if let path = URL(string: "someStringForUrl") {
do {
let data = try Data(contentsOf: path)
}
catch {
print("ERROR: \(error)")
}
}
...
You never reach the try, so you never reach the catch. Before any of that can happen, the path! forced unwrap fails and crashes the app.
(Crashing the app is a runtime exception, which is a completely different thing from the Swift try/catch mechanism. It doesn't not "cover all the runtime exceptions"; it doesn't cover any runtime exceptions. You're confusing apples with elephants; they are totally unrelated. Runtime exceptions are not thrown-caught Error objects, and vice versa.)

Catch' block is unreachable but I included try

I'm getting the following error:
'catch' block is unreachable because no errors are thrown in 'do' block
But my try is present, how can I include the error then?
AF.request(url).responseData(completionHandler: { data in
do {
if let apiJsonData = try? JSONDecoder().decode(MyModel.self, from: data.data!){
self.items = apiJsonData.data.items
}
} catch {
print("Decoding failed -> ERROR:", error)
}
}
If I include it in the header like: data, error in it throws an error:
Contextual closure type '(AFDataResponse<Data>) -> Void' (aka '(DataResponse<Data, AFError>) -> ()') expects 1 argument, but 2 were used in closure body
How can I have the try catch correctly setup?
You've used try?, not try.
try? turns the whole expression into nil if an error was thrown. It already handles the error on its own, so there's no more error for the catch block to catch. On the other hand, try doesn't do that. For more info, see the Error handling section of the Swift Guide.
If you want the error to be caught by the catch block, you should use try, and delete the if block.
AF.request(url).responseData(completionHandler: { data in
do {
let apiJsonData = try JSONDecoder().decode(MyModel.self, from: data.data!)
self.items = apiJsonData.data.items
} catch {
print("Decoding failed -> ERROR:", error)
}
}
Also note that you are assuming data.data is not nil here. This might not be the case if the network request fails. Better check for it:
AF.request(url).responseData(completionHandler: { data in
do {
guard let data = data.data else {
print("Response Error:", data.error)
return
}
let apiJsonData = try JSONDecoder().decode(MyModel.self, from: data.data!)
self.items = apiJsonData.data.items
} catch {
print("Decoding failed -> ERROR:", error)
}
}

Upgraded from Kanna 2.2.1 to 4.0.2 and getting the same error

I am rewriting a project I found on Github to learn and teach myself how to use swift and pod files. I upgraded Kanna from 2.2.1 to 4.0.2 because I was getting an arm64 error.
With 4.0.2 I am getting the error:
Initializer for conditional binding must have Optional type, not 'HTMLDocument'
Call can throw, but it is not marked with 'try' and the error is not handled
I am unsure about what this error means and how to fix it. It is associated with this if statement:
if let doc = Kanna.HTML(html: htmlText, encoding: String.Encoding.utf8) {
for itemSize in doc.css("option[value^='']") {
let itemSizeText = itemSize.text!.lowercased()
let wishListItemSize = self.websiteInstance!.websiteWishListItem.size!.lowercased()
if itemSizeText.range(of: wishListItemSize) != nil {
print("Found size")
foundItemSize = true
let itemSizeValue = itemSize["value"]
self.websiteInstance!.viewController!.websiteBrowser!.evaluateJavaScript("document.getElementById(\"size-options\").value = \(itemSizeValue!)", completionHandler: nil)
break
}
countSize += 1
}
}
The type signature for the method you are calling is public func HTML(html: String, url: String? = nil, encoding: String.Encoding, option: ParseOption = kDefaultHtmlParseOption) throws -> HTMLDocument. The function returns a non-Optional value, but can throw an error.
You can handle the error by either using the try? keyword to make the function return nil in case an error was thrown and make the optional binding you currently use work like this:
if let doc = try? Kanna.HTML(html: htmlText, encoding: String.Encoding.utf8) {...
or rather use try and put the function call in a do-catch block to see the actual error in case any was thrown.
do {
let doc = Kanna.HTML(html: htmlText, encoding: String.Encoding.utf8)
for itemSize in doc.css("option[value^='']") {
let itemSizeText = itemSize.text!.lowercased()
let wishListItemSize = self.websiteInstance!.websiteWishListItem.size!.lowercased()
if itemSizeText.range(of: wishListItemSize) != nil {
print("Found size")
foundItemSize = true
let itemSizeValue = itemSize["value"]
self.websiteInstance!.viewController!.websiteBrowser!.evaluateJavaScript("document.getElementById(\"size-options\").value = \(itemSizeValue!)", completionHandler: nil)
break
}
countSize += 1
}
} catch {
print(error)
// Handle error
}

Get error description from caught error

I do something like this:
let decoder = JSONDecoder()
do
{
let decodedData = try decoder.decode(type, from: data)
}
catch DecodingError.dataCorrupted
{
let descr = ???
Log.error("Failed to decode JSON response. Error was: \(descr)")
}
how can I access the error description from this? Why can I not simply catch any kind of error in one catch and access its debug description?
How to access the error description
In Swift, a lot of the errors conform to the protocol LocalizedError, which will give you a variable localizedDescription: String? that you can use to print an error message. DecodingError should not be any different.
How to catch any kind of error
You should be able to catch any kind of errors in one catch. In order to do this, you can use
catch let error as DecodingError {
// Any error of type DecodingError
}
or
catch {
// Any possible error
}
Putting it all together
If I understand correctly, you are tring to catch any error of type DecodingError. In that case, you can simply do the following
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(type, from: data)
} catch let error as? DecodingError {
Log.error("Failed to decode JSON response. Error was: \(String(describing: error.localizedDescription))")
}

Confused on Error Handling in Swift 3

I got confused error handling in swift3. I try to do like "if XX function got error then try YY function"
Let me show you what I try:
class MyClass {
enum error: Error
{
case nilString
}
func findURL() {
do {
let opt = try HTTP.GET(url_adr!)
opt.start { response in
if let err = response.error {
print("error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
do
{
/* This is func1. and got error. I want to if this function has error then go next function. */
try self.stringOperation(data: response.description)
}
catch{
print("doesn't work on func1. trying 2nd func")
self.stringOperation2(data:response.descritption)
}
}
} catch let error {
print("got an error creating the request: \(error)")
}
}
func stringOperation(data:String)throws -> Bool{
do{
/** 1 **/
if let _:String = try! data.substring(from: data.index(of: "var sources2")!){
print("its done")
}else{
throw error.nilString
}
IN 1: I got this fatal error on this line:
"fatal error: unexpectedly found nil while unwrapping an Optional value" and program crashed.
I googled error handling try to understand and apply to in my code. However not succeed yet. Can someone explain where did I wrong?
Additional info: I got String extension for .substring(from:...) , and .index(of:"str"). So these lines doesn't got you confused.
As a general rule, try avoiding using force unwrapping (!), where you have
if let _: String= try! data.substring...
Instead use
if let index = data.index(of: "var sources2"),
let _: String = try? data.substring(from: index) { ... } else { ... }
That way you remove the two force unwraps that may be causing your crash. You already have the if let protection for catching the nil value, so you can make the most of it by using the conditional unwrapping.