在回顾Swift Programing Language的as/is,在“Type Casting for Any and AnyObject”部分有句话
“The Any type represents values of any type, including optional types. Swift gives you a warning if you use an optional value where a value of type Any is expected. If you really do need to use an optional value as an Any value, you can use the as operator to explicitly cast the optional to Any, as shown below.
Var things: [Any] = []
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning”
// Warning的内容是
// Expression implicitly coerced from 'Int?' to 'Any'
// Provide a default value to avoid this warning
// Force-unwrap the value to avoid this warning
// Explicitly cast to 'Any' with 'as Any' to silence this warning
本来想查一下为什么会有warning,一查便发现原来Any和Optional一起用时是比较坑的,这里面引发问题的点是:
- Any是可以表示任意类型的实例,它是一个类型,同时Optional本身也是一种类型(enum),所以Any类型的变量也可以存储Optional的实例,nil作为Optional的一个取值,当然也就能被Any存储
- 但一旦将Optional类型实例赋值给Any后,可能会有一些不符合预期的现象发生
那会引发什么问题呢?
let a: Int? = nil
var b: Any = a
print(a == nil) // true
print(b == nil) // false
这样的输出结果乍一看是不是很反常:明明b也是nil,为什么判断却输出了false?
关于分析为什么输出false的过程可以参考模糊的 Any 和 Optional,另外也可以看下唐巧的Swift 烧脑体操(一) - Optional 的嵌套,其中会详细拆解Optional的结构,看上去更直观
此处我不做底层实现的分析,只说下直接原因:
- Swift中的nil不比OC的nil,OC的nil就是0,Swift的nil是一个Optional类型
- 关键问题在于,
b == nil
这一行最终会转变为,一个有明确值的Any类型实例与Optional<Any
>.none进行比较,或者说一个Any
类型的东西和一个Optional
类型(enum)的东西比较,两个不同类型的东西进行比较怎么可能相等呢
其实
print(b == nil) // false
这一行编译器是有Warning提醒的:Comparing non-optional value of type 'Any' to 'nil' always returns false
换个角度理解Any、Optional
模糊的 Any 和 Optional中对于上面print(b == nil) // false
有一句评价:我的想法是,至少在我们这些语言使用方(iOS 的开发者)的角度来看,这是错的!因为我们想比较的是一个
Optional 的值,是不是
nil ,而并不关注这个
Optional 最外层的枚举是不是
.some 。从这个角度上来看,这是不友好的一面。
我明白和理解作者说的只是想知道b是不是nil的诉求,但经过与Swift大佬进行交流后,我并不同意这样的的想法以及类似的代码设计
我倾向于从Swift语言本身来看待该问题
- Any作为一种兜底类型,本来就不该常用于代码中,需要用时必须想好,是不是真的有必要,是不是没有遵照基本的设计原则和设计模式,导致代码劣化进而做出这样的妥协
- Swift作为强类型的语言,不同类型之间比较时返回false,是很合理的
- 所以还是要反问一句,在编译器已经给出警告的情况下,为什么执意要
optionalNumber as Any
另外,作者在文中举了一个项目中具体的例子:
let a: Int = 0
let b: String = ""
let c: Int? = nil
let arr: [Any] = [a, b, c]
arr.compactMap { $0 }
print(arr) // [0, "", nil]
想要表达的意思也很明确,在不明确Optional和Any的特点的情况下会奇怪为什么compactMap没有将nil的内容过滤掉?
原因是:在将c放入arr中时,类型变为了Any,进行compactMap时,c与nil进行比较,又变成了本文开始的例子
那么我想的是:
- 为什么会有这样的代码让一个可以容纳一切的array出现,是不是设计有问题
- 即使真的要用一个存储任意类型的array出现,如果真的知道array中可能有Optional的值,那为什么没有设计成[Any?]呢,[Any?]的设计从语义上来讲不更符合这段代码逻辑嘛
let a: Int = 0
let b: String = ""
let c: Int? = nil
let arr: [Any] = [a, b, c]
let arr1: [Any?] = [a, b, c]
print(arr.compactMap { $0 }) // [0, "", nil]
print(arr1.compactMap { $0 }) // [0, ""]
最后,根据我的理解,给自己一点点建议:
- 慎用
Any
和嵌套的Optional
类型,考虑清楚是否有必要 - 建议仅用
Any
来存储非Optional的内容