This looks basically correct, but it's hard to see what's going on.
Imagine a dictionary of type [String: String]
. Ask for the value of a key:
let value = parameters[key]
Here, value
is of type String?
, because the subscript returns nil
if the key doesn't exist in the dictionary. If the value does exist, the returned value is that value as an optional, which must be unwrapped.
In other words, the dictionary lookup gives you either Optional<String>.none
or Optional<String>.some(aString)
, where aString
is a value you put in the dictionary.
However, your dictionary is more like type [String: String?]
, because it can contain optional values. Therefore, dictionary lookup gives you:
-
Optional<String?>.none
if the value is not in the dictionary, or
-
Optional<String?>.some(Optional<String>.none)
if the value is in the dictionary but the value is a nil string, or
-
Optional<String?>.some(Optional<String>.some(aString))
if the value is in the dictionary and is a non-nil string.
Your dictionary is actually [String: Any]
, which makes the double-level optional harder to see, and lldb
isn't very clear about it.
(lldb) p parameters[key]
(Any?) $R26 = nil
There is a value in the dictionary, but the value is a nil string. This is like Optional<String?>.some(Optional<String>.none)
in the above list. Looks just like Optional<String?>.none
, but it isn't, as the next command shows:
(lldb) p parameters[key] == nil
(Bool) $R28 = false
In that ==
expression, Swift's behavior is to treat the nil
on the RHS like Optional<String?>.none
. Since the LHS is actually like Optional<String?>.some(Optional<String>.none)
, there's no equality.
This is reinforced by the next command:
(lldb) p print(parameters[key])
Optional(nil)
() $R30 = {}
If the key was not in the dictionary, the printed result would be "nil", not "Optional(nil)". That is, printing something like Optional<String?>.none
gives "nil", but printing something like Optional<String?>.some(Optional<String>.none)
gives "Optional(nil)".
Lastly, but most confusingly:
p (parameters[key] as? String?) == nil
(Bool) $R32 = true
Now things are different, because the type of the LHS is String??
— the extra ?
comes from the as?
— and type inference forces Swift to treat the RHS as nil as String??
. In other words, you're now comparing something like Optional<String?>.some(Optional<String>.none)
with itself, which is true!
The moral of the story is: be very careful when putting optional values into a dictionary, and be very, very careful with lldb
's attempts to print things "nicely". It may be misleading you.
Note: I had to waffle in my explanation by saying "something like Optional<String?>
" because the type is actually Optional<Any?>
. However, if you try to rerun the explanation using Any
in place of String
, it gets incomprehensible, because mere mortals can't really tell Any
and Any?
apart. The compiler can, though, which is why it all works.