Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking for nil Value in Swift Dictionary Extension

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!

like image 283
Alex311 Avatar asked Mar 15 '16 17:03

Alex311


1 Answers

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 of nil. There is the nil that you get if the key is missing, and then the nil 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:

var d : [String:Int?] = ["Matt":1]
let val = d["Matt"]

What is the type of val? It's Int?? - 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:

var d : [String:Int?] = ["Matt":nil]
let val = d["Matt"]

What is val? Well, the playground may say it is nil, but the truth is more complicated; it's nil wrapped in another Optional. That is easiest to see if you print val, in which case you get, not nil, but "Optional(nil)".

But if we try for something where the key isn't there at all, we get a different answer:

let val2 = d["Alex"]

That really is nil, signifying that the key is missing. And if we print val2, we get "nil". The playground fails to make the distinction (it says nil for both val and val2), but converting to a String (which is what print 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.

like image 113
matt Avatar answered Sep 26 '22 23:09

matt