Unwrapping Any, in Swift
If Any is not nil, except when it is, how do we check it and unwrap the underlying?
Did you know Any
can be nil
? It both makes sense and doesn't. But did you know Optional<Int>.none as Any == nil
would return false
? No? Me neither.
What else doesn't work then?
The issue with an optional Any
A basic direct equality comparison
let value: Any = Optional<Int>.none as Any
if value == nil {
print("Value \(value) is nil")
} else {
print("Value \(value) is not nil") // This branch is taken.
}
This check fails to recognize it as nil. This behaviour isn’t limited to Optional<Int>—any optional, like Optional<String>.none, behaves the same way when cast to Any.
Optional binding
let value: Any = Optional<Int>.none as Any
if let unwrapped = value as? Int {
print("Value \(value) is not nil") // This branch is taken.
} else {
print("Value \(value) is nil")
}
If case pattern matching
let value: Any = Optional<Int>.none as Any
if case nil = value {
print("Value \(value) is nil")
} else {
print("Value \(value) is not nil") // This branch is taken.
}
Switch pattern matching
let value: Any = Optional<Int>.none as Any
switch value {
case nil:
print("Value \(value) is nil")
default:
print("Value \(value) is not nil") // This branch is taken.
}
If we try to match an Any value against nil in a switch or an if case statement, the pattern won’t match even when the underlying optional is .none.
Iterating over an array of heterogenous optional items
But it should be obvious, considering the first example
let values: [Any] = [Optional<Int>.none as Any, 42, "Swift"]
for (index, value) in values.enumerated() {
// Even though value appears "nil" when printed,
// the equality check with nil returns false.
print(item)
if value == nil {
print("Value \(value) at index \(index) is nil")
} else {
print("Value \(value) at index \(index) is not nil")
}
}
It will gladly print "Value nil at index 0 is not nil"
The reason, and the solution
Now, there are reasons for this behaviour. But if, like me, you need to check whether or not your Any
is nil
, they won't help you much. Luckily, we can still unwrap our optional Any
or test for nil
by comparing to Optional<Any>.none
instead of nil
. For example:
let value: Any = Optional<Int>.none as Any
switch value {
case Optional<Any>.none:
print("Value \(value) is nil") // This branch is taken.
case Optional<Any>.some(let unwrapped):
print("Value \(unwrapped) is not nil")
default:
print("Value \(value) is not nil")
}
Courtesy of vacawama on Stack Overflow.

Comments ()