Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialMaxence Roy
8,753 PointsError handling challenge in Swift
Puzzled on this one. Too much new stuff at once. What am I doing wrong?
enum ParserError: Error {
case emptyDictionary
case invalidKey
}
struct Parser {
var data: [String : String?]?
func parse(data: [String : String?]?) throws {
guard let data != nil else {
throw ParserError.emptyDictionary
}
guard let someKey = data["someKey"]? else {
throw ParserError.invalidKey
}
}
}
let data: [String : String?]? = ["someKey": nil]
let parser = Parser(data: data)
Maxence Roy
8,753 PointsYou can click on the top right of the screen, the "View Challenge" button.
2 Answers
Jhoan Arango
14,575 PointsHello,
You were close, using the guard statement in this type of situation is ideal. But there are some few things you have to remember when using the guard statement you can use it in two ways:
- When unwrapping an optional, just like the "if let" statement
- When checking for a boolean value such as "if else" statement.
In this case we need to unwrap an optional and also check for a condition.
enum ParserError: Error {
case emptyDictionary
case invalidKey
}
struct Parser {
var data: [String : String?]?
func parse() throws {
// Here we check for a condition
guard data?.isEmpty == false else {
throw ParserError.emptyDictionary
}
// Here we unwrap the keys, and then check for a condition,
guard let keys = data?.keys, keys.contains("someKey") else {
throw ParserError.invalidKey
}
}
}
let data: [String : String?]? = ["someKey": nil]
let parser = Parser(data: data)
When unwrapping an optional value, remember to create a temporary store property.
// example
guard let someProperty = someOptional else { return }
Hope this helps
Good luck
Also don't forget that if this answer helps you understand better, don't forget to select as best answer, to help others find a good answer. Or if you need a better explanation please let me know.
Maxence Roy
8,753 PointsThanks a lot! That really unblocked me and I was able to complete the challenge.
Olivier Van hamme
5,418 PointsIf you create a separate local constantโe.g. let someKeyโfor keys.contains("someKey"), you get an error saying that The initializer for conditional binding must have Optional type, not 'Bool'.
Why is that? As your example is doing something similar without declaring a local constant. Yet, with your solution, the compiler does not throw an error.
Jhoan Arango
14,575 Points // Here we unwrap the keys, and then check for a condition,
guard let keys = data?.keys, keys.contains("someKey") else {
throw ParserError.invalidKey
}
This guard statement is doing 2 things. First is unwrapping an optional value data?.keys and assigning it to a constant called keys, then It uses that constant to check if it contains "someKey". The contains() method returns a boolean.
If you do something like:
guard let someValue = keys.contains("someKey") else { return } // Error
It will give you an error because the "contains()" method is not optional, is actually returning a boolean.
Instead do this:
guard keys.contains("someKey") else { return }
// which is the same as
if keys.contains("someKey") {
//Do something
} else {
// Do something else
}
Remember a guard statement will allow the process to continue if the condition is true, if not it will "return", if you want for it to do something if the condition is not true, then you should add something before it returns.
guard keys.contains("someKey") else {
// do something if false
return }
// Will continue if true
Hope this helps you, let me know if you have any other questions.
Olivier Van hamme
5,418 PointsThank you for your swift response Jhoan :)
Unfortunately, things are not completely clear yet.
Here's my original code for the code challenge:
struct Parser {
var data: [String : String?]?
func parse() throws {
guard let dictionary = data else {
throw ParserError.emptyDictionary
}
guard
let dictionary = data, // ERROR 1
let dictionaryKeys = dictionary.keys, // ERROR 2
let someKey = dictionaryKeys.contains("someKey") else {
throw ParserError.invalidKey
}
}
}
// ERROR 1: Definition conflicts with previous value.
// ERROR 2: Initializer for conditional binding must have Optional type, not 'Dictionary<String, String?>.Keys'
There are two things that need clearing up:
(1) I thought that a local constant only exists within the guard statement? Apparently not, hence ERROR 1?
(2) Why does the second guard statement not let me unwrap each value one by one: from dictionary to dictionaryKeys, to someKey? Why can't I use the unwrapped dictionary constant for defining the dictionaryKeys constant? ERROR 2 is still a mystery to me, as I have securely unwrapped each value?
NOTE: I just realised that dictionary.keys returns an Array, which is not an optional. An Array does not contain a nil value unlike a dictionary that has a nil enum case by default. So, let dictionaryKeys can only have a non optional value. It will blow up if data is just an empty Dictionary because data = [:] is a valid empty dictionary?
NOTE: I have noticed that I cannot OPTION click on let dictionaryKeys and let somekey to reveal the type.
Jhoan Arango
14,575 PointsHello Oliver,
struct Parser {
var data: [String : String?]?
func parse() throws {
guard let dictionary = data else {
throw ParserError.emptyDictionary
}
guard
let dictionary = data, // ERROR 1
let dictionaryKeys = dictionary.keys, // ERROR 2
let someKey = dictionaryKeys.contains("someKey") else {
throw ParserError.invalidKey
}
}
}
// ERROR 1: Definition conflicts with previous value.
// ERROR 2: Initializer for conditional binding must have Optional type, not 'Dictionary<String, String?>.Keys'
//-------
// ERROR 1: You defined the dictionary constant in the first guard, this constant may be use outside of its body.
// When a guard defines a constant or variable, and the condition is successful, you may use that constant after the guard statement.
// ERROR 2: Since you are "guarding" data, and defining a constant as "dictionary", it means that "dictionary" has values in it and is no longer an optional. So that second step is invalid because guards only work if there is an optional. ( There is nothing to unwrap ).
Here is your approach but with some changes
struct Parser {
var data: [String : String?]?
func parse() throws {
guard let dictionary = data else {
throw ParserError.emptyDictionary
}
for key in dictionary.keys {
if key != "someKey" {
throw ParserError.invalidKey
}
}
}
}
// OR
/// I personally like this approach better
struct Parser {
var data: [String : String?]?
func parse() throws {
guard let dictionary = data else {
throw ParserError.emptyDictionary
}
guard dictionary.keys.contains("someKey") else {
throw ParserError.invalidKey
}
}
}
You were on the right track, you were just missing the concept of the guard statement when unwrapping an optional, and I know it can be confusing because the guard statement is an early exit statement.. Meaning that if the condition does not pass, it halts all the execution after it.
This is from Apple's "The Swift Programming Language" Book. You should have it handy
Early Exit A guard statement, like an if statement, executes statements depending on the Boolean value of an expression. You use a guard statement to require that a condition must be true in order for the code after the guard statement to be executed. Unlike an if statement, a guard statement always has an else clauseโthe code inside the else clause is executed if the condition is not true.
Excerpt From: Apple Inc. โThe Swift Programming Language (Swift 4.1).โ Apple Books. https://itunes.apple.com/us/book/the-swift-programming-language-swift-4-2/id881256329?mt=11
Hope this helps
Olivier Van hamme
5,418 PointsCheers Jhoan, very much appreciated. Lesson learned: There's no need to unwrap the optional for a second time if it has passed a guard statement.
Leong Sze Kim
9,179 PointsLeong Sze Kim
9,179 PointsWhat is the question about ?