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
- You should avoid force unwrapping as much as possible. Instead you managed to put 3
!
on a single line! - 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:
itemID
can't be converted to anInt
: This would mean thatitemKey
will benil
, hence, the second part would't even be tested and the content of the if wouldn't be executed.itemID
can be converted to anInt
, but it is not an existing key: In this caseitemKey
would be set to theInt
itemID
gets converted to. Then, the second statement would be tested, but sinceitemKey
would not be found as an existing key,myDictionary[itemKey]
would returnnil
and again, the content of the if would not be executed.itemID
can be converted to anInt
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 isnil
(like in[3: nil]
).
Related Topics
Creating a Custom Scngeometry Polygon Plane with Scngeometryprimitivetype Polygon Crash/Error
Adding a Search Bar to Navigationview in Swiftui
Swift - 'Bool' Is Not a Subtype of 'Void'
Swift 4 Codable - API Provides Sometimes an Int Sometimes a String
Understanding the Removerange(_:) Documentation
Adding a View to the Window Hierarchy
Masking Uiview/Uiimageview to Cutout Transparent Text
How to Continue Ble Activities Onto Next View Controller
How to Setup a Second Component with a Uipickerview
Realm Mobile Platform, How to Connect While Offline
Enum with Identical Cases Names with Associated Values of Different Types
Sending a Parameter Argument to Function Through Uitapgesturerecognizer Selector
What Is Other Option Available in Swift Instead of Refactoring and Renaming Class or Attribute Name
How to Make Firebase Database Data the Data Source for Uicollection View
Nsbundle.Mainbundle().Urlforresource("Bach1", Withextension: "Jpg") Returning Null
Dispatch Group - Cannot Notify to Main Thread