iOS Error Handling in Swift 2.0 Error Handling Handling Errors

JUNSEI TEI
JUNSEI TEI
2,055 Points

Error Handling

error.swift
enum ParserError: ErrorType {
    case EmptyDictionary
    case InvalidKey
}
struct Parser {
    var data: [String : String?]?

    func parse() throws {
        guard data?.isEmpty == false else {
            throw ParserError.EmptyDictionary
        }
        guard let a = data?["someKey"]  else {
            throw ParserError.InvalidKey        }
    }

}
let data: [String : String?]? = ["someKey": nil]

do {
    let parser = Parser(data: data)
   try parser.parse()

} catch ParserError.InvalidKey {
    print("key does not exist")
} catch ParserError.EmptyDictionary {
print("The data is nill")
}

5 Answers

Steven Deutsch
Steven Deutsch
21,044 Points

Hey JUNSEI TEI,

Let's break down this challenge step by step. It's a tough one. The first task asks us to do two things.

  1. Check if our parser data is equal to nil
  2. Check if the data contains a key called "someKey"

If any of these two checks is not true, we want to throw an error.

1) To check if the data is equal to nil, we can use a guard statement to do optional binding. If data is equal to nil, we will immediately execute the else block and throw the error .EmptyDictionary.

2) To check if the data contains a key called "someKey" we will also use a guard statement with optional binding. First we access the keys property of data. This property returns an array of keys from the data dictionary. However, because data is an optional value, before we use dot syntax to access the property, we must conditionally unwrap it.

Next we have to use a where clause to add an additional check to this statement. If accessing the keys property on data did not return nil, the array of keys will be assigned to the local constant named keysArray. We then want to check if that keysArray contains the key "someKey". We can do this with the contains() method. If the keysArray does not contain "someKey", the else block will be executed and we will throw the error .InvalidKey.

The second/third tasks asks us to:

  1. Call the parser function on the parser data
  2. Handle the errors thrown

1) Because the parser method has been marked as a function that throws, in order to call it we need to use the 'try' keyword. When calling functions that throw errors, it is typical to do them inside a do/catch block. The do block is where we do our function calls, and the catch block is where we handle the errors that are thrown.

2) Now for each catch block we can execute some sort of functionality for each type of error thrown. You do this by writing the name of the enum and its specific member that you are checking for. In this example, we are just going to print a message to the console to give the user a little information about which error was thrown.

enum ParserError: ErrorType {
  case EmptyDictionary
  case InvalidKey
}

struct Parser {
  var data: [String : String?]?

  func parse() throws {
    guard let parserData = data else {
        throw ParserError.EmptyDictionary
    }

    guard let keysArray = data?.keys where keysArray.contains("someKey") else {
        throw ParserError.InvalidKey
    }

  }
}

let data: [String : String?]? = ["someKey": nil]
let parser = Parser(data: data)

do {
  try parser.parse()
} catch ParserError.EmptyDictionary {
  print("The dictionary was empty.")
} catch ParserError.InvalidKey {
  print("The dictionary did not contain the specified key.")
}

Hope this helps!

JUNSEI TEI
JUNSEI TEI
2,055 Points

Your answer helps me a lot!!! Thx

Dustin Bryce Flanary
Dustin Bryce Flanary
13,349 Points

I especially appreciated your explanation, thank you!

Steven provided a wonderful explanation of the concepts in this challenge above (thanks!!), but unfortunately that code no longer works for this challenge as it was posted a while back. This was a tricky one for me, so I'm adding the answer that worked for me below (currently Swift 4) in the hopes it helps someone else out:

enum ParserError: Error {
    case emptyDictionary
    case invalidKey
}

struct Parser {
    var data: [String : String?]?

    func parse() throws {
        guard data != nil else {
            throw ParserError.emptyDictionary
        }

        guard data?.keys.contains("someKey") == true else {
            throw ParserError.invalidKey
        }
    }
}

let data: [String : String?]? = ["someKey": nil]
let parser = Parser(data: data)

do {
    try parser.parse()
} catch ParserError.emptyDictionary {
    print("The dictionary is empty.")
} catch ParserError.invalidKey {
    print("The key 'someKey' does not exist.")
}

These long questions are the ones that eventually I just end up using copy/paste if I'm honest.

It really doesn't feel like a good task for beginners...

Thank you so much though!

Alex Millius
Alex Millius
iOS Development Techdegree Student 5,468 Points

Thanks for your response Steven Deutsch.

There is a small room for improvement in your code.

In your function parse, your guard is:

guard let a = data?["someKey"]  else {
       throw ParserError.InvalidKey        
}

This way you defined a value "a" that is never used. This is not a good practice.

Maybe you could replace it by a "!= nil" check.

guard data?["someKey"] != nil  else {
       throw ParserError.InvalidKey
}
Steven Deutsch
Steven Deutsch
21,044 Points

Oh very interesting Alex. I like it. Good point!

John Hendrix
John Hendrix
21,829 Points

The above coded answer no longer compiles in 2017. Can someone help with the correct answer. I have tried everything I can think of with no luck so far.

Bummer! Your code could not be compiled. Please click on "Preview" to view the compiler errors.

swift_lint.swift:16:39: error: expected expression in conditional guard let keysArray = data?.keys, where keysArray.contains("someKey") else { ^ swift_lint.swift:16:80: error: braced block of statements is an unused closure guard let keysArray = data?.keys, where keysArray.contains("someKey") else { ^ swift_lint.swift:3:19: error: 'ErrorType' has been renamed to 'Error' enum ParserError: ErrorType { ^~~~~~~~~ Error swift_lint.swift:3:6: error: type 'ParserError' does not conform to protocol 'RawRepresentable' enum ParserError: ErrorType { ^ Swift.RawRepresentable:96:20: note: protocol requires nested type 'RawValue'; do you want to add it? associatedtype RawValue ^ swift_lint.swift:13:27: error: thrown expression type 'ParserError' does not conform to 'Error' throw ParserError.EmptyDictionary ~~~~~~~~~~^~~~~~~~~~~~~ swift_lint.swift:17:27: error: thrown expression type 'ParserError' does not conform to 'Error' throw ParserError.InvalidKey ~~~~~~~~~~^~~~~~~~ swift_lint.swift:16:80: error: expression resolves to an unused function guard let keysArray = data?.keys, where keysArray.contains("someKey") else { ^

Bekzod Rakhmatov
Bekzod Rakhmatov
7,419 Points

Did you get the solution of this problem? (https://teamtreehouse.com/community/swift-3-error-handling-code-challenge-help) somebody left this solution it may help you out there!

John Hendrix
John Hendrix
21,829 Points

Hi Bekzod Rakhmatov , I did finally get it, but thank you for checking and thanks for providing the link for others!