I have the following code in a playground file:
extension Dictionary {
func test() {
for key in self.keys {
self[key]
}
}
}
var dict: [String: AnyObject?] = [
"test": nil
]
dict.test()
I will henceforth refer to the line within the for-each loop as the output since it is what's relevant. In this particular instance the output is nil
.
When I change the for-each loop to look like this:
for key in self.keys {
print(self[key])
}
The output is "Optional(nil)\n"
.
What I really want to do is check the value for nil, but the code:
for key in self.keys {
self[key] == nil
}
outputs false
.
One other thing I tried was the following:
for key in self.keys {
self[key] as! AnyObject? == nil
}
which produces the error:
Could not cast value of type 'Swift.Optional<Swift.AnyObject>' to 'Swift.AnyObject'
Any help with this is much appreciated!
Checking if a dictionary value is
nil
makes sense only if the dictionary values are anOptional
type, which means that you have to restrict your extension method to that case.This is possible by defining a protocol that all optional types conform to (compare How can I write a function that will unwrap a generic property in swift assuming it is an optional type?, which is just a slight modification of Creating an extension to filter nils from an Array in Swift):
Now you can define an extension method with is restricted to dictionaries of optional value types:
As Matt already explained,
self[key]
can only benil
if that key is missing in the dictionary, and that can not happen here. So you can always retrieve the value for that keyinside that loop. Better, enumerate over keys and values:
Now
value
is the dictionary value (forkey
), and its type conforms toOptionalType
. Using theasOptional
protocol property you get an optional which can be tested against nil:or used with optional binding.
Putting all that together:
Example:
Output:
You've gotten yourself into kind a mess, because a dictionary whose values can be
nil
presents you with the prospect of a double-wrapped Optional, and therefore two kinds ofnil
. There is thenil
that you get if the key is missing, and then thenil
that you get if the key is not missing and you unwrap the fetched result. And unfortunately, you're testing in a playground, which is a poor venue for exploring the distinction.To see what I mean, consider just the following:
What is the type of
val
? It'sInt??
- an Int wrapped in an Optional wrapped in another Optional. That's because the value inside the dictionary was, by definition, an Int wrapped in an Optional, and then fetching the value by its key wraps that in another Optional.So now let's go a bit further and do it this way:
What is
val
? Well, the playground may say it isnil
, but the truth is more complicated; it'snil
wrapped in another Optional. That is easiest to see if you printval
, in which case you get, notnil
, but"Optional(nil)"
.But if we try for something where the key isn't there at all, we get a different answer:
That really is
nil
, signifying that the key is missing. And if we printval2
, we get"nil"
. The playground fails to make the distinction (it saysnil
for bothval
andval2
), but converting to a String (which is whatprint
does) shows the difference.So part of the problem is this whole double-wrapped Optional thing, and the other part is that the Playground represents a double-wrapped Optional in a very misleading way.
MORAL 1: A dictionary whose value type is an Optional can get really tricky.
MORAL 2: Playgrounds are the work of the devil. Avoid them. Use a real app, and use logging to the console, or the variables pane of the debugger, to see what's really happening.