Why Do I Still Need to Unwrap Swift Dictionary Value

Why do I still need to unwrap Swift dictionary value?

Dictionary accessor returns optional of its value type because it does not "know" run-time whether certain key is there in the dictionary or not. If it's present, then the associated value is returned, but if it's not then you get nil.

From the documentation:

You can also use subscript syntax to retrieve a value from the dictionary for a particular key. Because it is possible to request a key for which no value exists, a dictionary’s subscript returns an optional value of the dictionary’s value type. If the dictionary contains a value for the requested key, the subscript returns an optional value containing the existing value for that key. Otherwise, the subscript returns nil...

In order to handle the situation properly you need to unwrap the returned optional.

There are several ways:

Option 1:

func test(){
let type = "x"
if var data = X.global[type] {
// Do something with data
}
}

Option 2:

func test(){
let type = "x"
guard var data = X.global[type] else {
// Handle missing value for "type", then either "return" or "break"
}

// Do something with data
}

Option 3:

func test(){
let type = "x"
var data = X.global[type] ?? "Default value for missing keys"
}

Dictionary containing an array - Why is unwrapping necessary?

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

How to safely unwrap a dictionary value in Swift 4?

When using if let the email is visible only inside the block , You need

func emailAddress(for name: String) -> String {
let emails = ["daniel":"danielsenga@yahoo.com","Kevin":"Kevin@gmail.com"]
return emails[name] ?? "none"
}

For you code it would be like

func emailAddress(for name: String) -> String? {
let emails = ["daniel":"danielsenga@yahoo.com","Kevin":"Kevin@gmail.com"]
if let email = emails[name] {
print(email)
return email
}
print("There is no email address")
return nil
}

Swift Optional Dictionary [String: String?] unwrapping error

When you extract a value from a dictionary like this using subscript

[String: String?]

you need to manage 2 levels of optional. The first one because the subscript returns an optional. The second one because the value of you dictionary is an optional String.

So when you write

if let value = preferenceSpecification["someKey"] {

}

you get value defined as an optional String.

Here's the code to fix that

if let
optionalKey = preferenceSpecification["Key"],
key = optionalKey,
optionalDefaultValueKey = preferenceSpecification["DefaultValue"],
defaultValueKey = optionalDefaultValueKey,
value = preferenceSpecification[defaultValueKey] {
defaultsToRegister[key] = value
}

Suggestions

  1. You should avoid force unwrapping as much as possible. Instead you managed to put 3 ! on a single line!
  2. You should also try to use better name for your constants and variables.

Avoiding string to be considered optional in swift

The issue is that dict[index] is still returning an optional so str has to be an optional. There are two ways you can fix it

You can use the nil coalescing operator to use dict[index] if its not nil otherwise use "N/A"

let str = dict[index] ?? "N/A"

Another option is to use the dictionary default value subscript

let str = dict[index, default: "N/A"]

Why does implicitly unwrapped optional not unwrap in dictionary of type [String : Any]

Under the rules set out by SE-0054, IUOs are only force unwrapped in contexts that demand their unwrapped type. In your case, the IUO doesn't need to be force unwrapped in order to be coerced to Any (as Any can represent any value), so it isn't.

This behaviour is discussed in more detail in these Q&As:

  • Swift 3 incorrect string interpolation with implicitly unwrapped Optionals
  • Implicitly unwrapped optional assign in Xcode 8

The fact that you end up with an ImplicitlyUnwrappedOptional value in your dictionary is legacy behaviour that has been removed in the latest Swift snapshots, in the future you will end up with an Optional value instead (as IUO is no longer a type).

One important thing to note here however (that I'm sure will trip up people) is that the printing of IUOs got changed in 4.1.

In Swift 4.0.3, your example prints like this:

var aString: String! = "hello"
var params : [String : Any] = [
"myString" : aString
]
print(params)
// This prints ["myString": hello]

giving you the illusion that the IUO was force unwrapped when coerced to Any. This however is just how IUOs were printed in Swift 4.0.3 – if they had a value, then they would print as that value, otherwise they would print as nil:

var aString: String! = nil
var params : [String : Any] = [
"myString" : aString
]
print(params)
// This prints ["myString": nil]

The reason why this changed in Swift 4.1 is that ImplicitlyUnwrappedOptional's conformance to Custom(Debug)StringConvertible was removed in this commit in order to make progress towards removing the type itself. So now ImplicitlyUnwrappedOptional values get printed using Swift's default printing mechanism (using reflection).

So, in a dictionary, you get the IUO's default debugDescription, which looks like this:

let aString: String! = "hello"
let params : [String : Any] = [
"myString" : aString
]
print(params)
// This prints ["myString": Swift.ImplicitlyUnwrappedOptional<Swift.String>.some("hello")]

If you had printed it on its own, you would get its default description, which looks like this:

let aString: String! = "hello"
print(aString) // some("hello")

This is because in Swift 4.1, the ImplicitlyUnwrappedOptional type is implemented in the same way as Optional, an enumeration with two cases:

public enum ImplicitlyUnwrappedOptional<Wrapped> : ExpressibleByNilLiteral {
// The compiler has special knowledge of the existence of
// `ImplicitlyUnwrappedOptional<Wrapped>`, but always interacts with it using
// the library intrinsics below.

/// The absence of a value. Typically written using the nil literal, `nil`.
case none

/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)

// ...
}

For an IUO with a payload value, Swift's default reflection will therefore print it as the case some containing the wrapped value.

But this is only temporary; the IUO type is currently (in Swift 4.1) deprecated, however it will be removed in Swift 4.2. The compiler was internally using the IUO type in quite a few places, which took quite a bit of work to remove. Therefore in 4.2 you'll have actual Optional values in your dictionary, which will print like Optional("hello").

What happens when a Swift if let ... on a Dictionary key fails?

It is not completely right, the problem is that the conversion to int is also optional. For example, this would crash (or rather, you would get a compiling error):

let myDictionary = [3: "3"]
let itemID = "b"
if let myTestKonstant = myDictionary[Int(itemID)] {
print(myTestKonstant)
}

This would be the save way:

if let itemKey = Int(itemID), let myTestKonstant = myDictionary[itemKey] {
print(myTestKonstant)
}

UPDATE

So it is clearer, I will explain what would happen in different cases:

  1. itemID can't be converted to an Int: This would mean that itemKey will be nil, hence, the second part would't even be tested and the content of the if wouldn't be executed.

  2. itemID can be converted to an Int, but it is not an existing key: In this case itemKey would be set to the Int itemID gets converted to. Then, the second statement would be tested, but since itemKey would not be found as an existing key, myDictionary[itemKey] would return nil and again, the content of the if would not be executed.

  3. itemID can be converted to an Int that exists as key of the dictionary. Like in the previous case, itemKey will be set, and since the key is found, myTestKonstant will be filled and the content of the if will be executed, unless the corresponding value of the key is nil (like in [3: nil]).



Related Topics



Leave a reply



Submit