-1

I'm trying to wrap my head around why what feels intuitive is illegal when it comes to a dictionary containing an array in Swift.

Suppose I have:

 var arr = [1,2,3,4,5]
 var dict = ["a":1, "b":2, "test":arr]

I can easily access the dictionary members like so:

 dict["a"]
 dict["b"]
 dict["test"]

As expected, each of these will return the stored value, including the array for key "test":

[1,2,3,4,5]

My intuitive reaction to this based on other languages is that this should be legal:

 dict["test"][2]

Which I would expect to return 3. Of course, this doesn't work in Swift. After lots of tinkering I realize that this is the proper way to do this:

 dict["test"]!.objectAtIndex(2)

Realizing this, I return to my intuitive approach and say, "Well then, this should work too:"

 dict["test"]![2]

Which it does... What I don't really get is why the unwrap isn't implied by the array dereference. What am I missing in the way that Swift "thinks?"

David Hoelzer
  • 15,862
  • 4
  • 48
  • 67

3 Answers3

4

All dictionary lookups in Swift return Optional variables.

Why? Because the key you are looking up might not exist. If the key doesn't exist, the dictionary returns nil. Since nil can be returned, the lookup type has to be an Optional.

Because the dictionary lookup returns an Optional value, you must unwrap it. This can be done in various ways. A safe way to deal with this is to use Optional Chaining combined with Optional Binding:

if let value = dict["test"]?[2] {
    print(value)
}

In this case, if "test" is not a valid key, the entire chain dict["test"]?[2] will return nil and the Optional Binding if let will fail so the print will never happen.

If you force unwrap the dictionary access, it will crash if the key does not exist:

dict["test"]![2] // this will crash if "test" is not a valid key
vacawama
  • 150,663
  • 30
  • 266
  • 294
1

In the Objective-C world, it has potential crash if you are trying to access 3 by dict[@"test"][2]. What if dict[@"test"] is nil or the array you get from dict[@"test"] has two elements only? Of course, you already knew the data is just like that. Then, it has no problem at all.

What if the data is fetched from the backend and it has some problems with it? This will still go through the compiler but the application crashes at runtime. Users are not programmers and they only know: The app crashes. Probably, they don't want to use it anymore. So, the bottom line is: No Crashes at all.

Swift introduces a type called Optional Type which means value might be missing so that the codes are safe at runtime. Inside the Swift, it's actually trying to implement an if-else to examine whether data is missing.

For your case, I will separate two parts:

Part 1: dict["test"]! is telling compiler to ignore if-else statement and return the value no matter what. Instead, dict["test"]? will return nil if the value if missing. The terminology is Explicit Unwrapping

Part 2: dict["test"]?[2] has potential crash. What if dict["test"]? returns a valid array and it only has two element? This way to store the data is the same using dict[@"test"][2] in Objective-C. That's why it has something called Optional Type Unwrapping. It will only go the if branch when valid data is there. The safest way:

if let element = dict["test"]?[2] {
    // do your stuff
}
Lucas Huang
  • 3,998
  • 3
  • 20
  • 29
1

The problem is that Dictionary’s key-based subscript returns an optional – because the key might not be present.

The easiest way to achieve your goal is via optional chaining. This compiles:

dict["test"]?[2]

Note, though, that the result will still be optional (i.e. if there were no key test, then you could get nil back and the second subscript will not be run). So you may still have to unwrap it later. But it differs from ! in that if the value isn’t present your program won’t crash, but rather just evaluate nil for the optional result.

See here for a long list of ways to handle optionals, or here for a bit more background on optionals.

Community
  • 1
  • 1
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118