以下代码编译时不发出警告:
版本1:
var anything: Any
anything = "woof"
意义。。。任何是任何类型、值类型或引用类型。
但是,如果我们创建一个可选变量,例如Double?
,此代码会抛出警告:
版本2:
var anything: Any
let aDouble: Double? = 3
anything = aDouble
但是此代码不会引发警告:
版本3:
enum AnEnum<T>: {
case first
case second (T)
}
var anEnum: AnEnum<String> = .first
anything = anEnum
您可以合理化版本 2 引发警告,因为Any
不是Optional
类型,而Double?
是Optional
类型。 尝试将 Optional 分配给非可选类型是一种类型不匹配。
但是,在幕后,Optional
是具有.none
案例的enum
,以及具有.some
案例的.some
案例具有相关值。我的版本 3 使用了一个enum
,AnEnum
,它也有 2 个案例,其中第二个具有关联的值。AnEnum
类型与 Swift 本机Optional
类型几乎相同。
为什么将 AnEnum 值分配给anything
没问题,但将可选值分配给anything
却不行?
(我开始回答这个问题:具有混合类型(可选和非可选)的 Swift 字典)
然后意识到我真的不知道答案。
Optional
不仅仅是一个枚举。这也是每种类型的隐含促销。Any
和Optional
以奇怪的方式结合在一起,这可能会令人惊讶。例如,T?
是Any
。T??
也是Any
.Any
是Any
,Any?
是Any
。Any??
也是Any
.
这造成了许多令人头疼的问题,微妙的上投和下投使得人们非常不清楚Any
的可选性到底是什么。例如。
var anything: Any = ""
let aDouble: Double? = 3
anything = aDouble as Any
let x = anything as? Double
这里x
什么?好吧,aDouble
是Optional<Double>
,而不是Double
,所以as? Double
应该返回nil
。但这并不是因为魔法。它返回Optional(3)
.坦率地说,这有点奇怪(尝试使用MyOptional版本作为枚举获得相同的东西),但另一种方式会非常糟糕。考虑:
let data: [String: Any] = ["aDouble": aDouble]
let y = data["aDouble"] as? Double
此处Double?
y
,而不是Double??
因为可选的合并。谢天谢地。但这很奇怪。如果你有泛型涉及基于Optional
的一致性,这可能会变得非常混乱(确实如此;关于这个问题会出现很多问题......现在Double
上面用Any
替换,它很快就会出现在你身上。
因此,编译器警告您,将Any
与Optional
混合是一个坏主意,可能会导致奇怪的类型结果,并且您绝对应该在任何时候调用它。(也许你应该停止使用Any
因为它几乎从来都不是你真正想要的类型。
为了使它成为我能想到的最具体的例子,请考虑:
var anything: Any = ""
let one: Int = 1
let maybeTwo: Int? = 2
anything = one
anything as? Int // fine
anything = maybeTwo
anything as? Int? // illegal (see below)
anything as? Int // fine (but why? that's not what we put in.)
你会认为这将是平行的,但事实并非如此。你不能只是拉出你把 Optionals 扔到混合中时输入的类型。
顺便说一句,有趣的附录,你不能从Any
向下转换为Optional
类型。
let x = anything as? Double? // Illegal
这真的很有趣,因为anything
是一个Double?
:
(lldb) p anything
(Any) $R4 = {
payload_data_0 = 0x4008000000000000
payload_data_1 = 0x0000000000000000
payload_data_2 = 0x0000000000000000
instance_type = Double?
}
那么我如何区分"anything
是Double?
但为零"与"anything
是String
"之间的区别呢?你会认为你可以测试它,但我认为你不能,因为这些都不能始终如一地工作。相反,它试图方便。但是你不能用力过猛,否则所有的接缝都会显露出来。
很简单,这是因为 Any 就像一个可选的罗奇汽车旅馆。
罗奇汽车旅馆是一个蟑螂陷阱,其座右铭是"蟑螂入住,但他们不退房。对于"任何"和"可选"也是如此。您可以将 Optional 放入 Any 中,但您再也无法将其取出。
为了理解我的意思,让我们首先将其他东西放入 Any 中,然后再次将其取出:
let s : String = "howdy"
let any : Any = s
let s2 = any as! String
s2 // "howdy"
现在让我们尝试使用可选:
let s : String? = "howdy"
let any : Any = s
let s2 = any as! String? // error
哎呀!您不能将非可选"向下"转换为可选,因此原始"可选"将丢失。
由可选包装的东西不会丢失。您仍然可以解开包装:
let s : String? = "howdy"
let any : Any = s
let s2 = any as! String
s2 // "howdy"
但现在s2
是一个字符串,而不是一个可选。可选永远消失了。你无法把它拿出来。您无法发现放入"任何"的内容是可选的。不见了。
所以这就是为什么将 Optional 放入 Any 总是会引起警告的原因。编译器说:"你可以这样做,但你真的明白你在做什么吗?如果您这样做,有一些方法可以使警告静音(错误消息会告诉您它们是什么)。
它有自己的历史。
从这个功能开始,在 Swift 3 时代。
SE-0116 导入 Objective-C id 作为 Swift 任何类型(所以,在 Swift 的历史上被称为id-as-Any
,在我看来,这是历史上最糟糕的事情。
(在那之前,Any
在 Swift 程序员中并不是一个受欢迎的类型。
它包括一个很棒的新功能,通用桥接转换,它保证了 Swift 中的Any
值在桥接到 Objective-C 时可以转换为非空id
。
但是这个特征造成了大量灾难性的悲剧,称为_SwiftValue
。
在 Swift 3 中首次引入的原始转换中,无论它是nil
还是非nil
,Optional<Double>
都转换为_SwiftValue
。它在 Objective-C 世界中是完全没用的,几乎所有对_SwiftValue
的操作都会导致崩溃,直到运行时才能发现,丢弃了强类型语言的优点。
因此,Swift 团队需要立即引入一项新功能:
SE-0140 当可选转换为任意时发出警告,并将可选桥接为其有效负载或 NSNull
自从 Swift 3.0.1 中引入此功能以来,nil
被桥接到NSNull
值,非nil
值被桥接基于解开的值进行桥接。
该功能还包括将Optional
转换为Any
时的警告,以避免无意中滥用Optional
来Any
。
您描述的警告在 Swift 历史记录中的这个时候开始。
Optional
的行为得到了许多编译器魔术(Swift 团队成员所谓的)的支持,例如,你不能对其他枚举类型使用简单的 if-let,你不能对你的自定义枚举使用可选链接。Swift 编译器将Optional
视为比普通枚举更特殊的东西。
因为你可以有一个Optional<Any>
,通过做Any?
。编译器不知道您期望anything
类型是nil
还是Optional.none
因此它会发出警告以确保您了解情况。
以下是AnEnum
与Optional
的不同之处。
您可以通过实现Optional
所做的一切来模仿Optional
。但是,您创建的是另一个独立的"可选",无法与 Swift 内置Optional
互操作。例如,您可以实现ExpressibleByNilLiteral
以便将nil
分配给解析为.first
的AnEnum
。就像Optional
如何nil
Optional.none
enum AnEnum<T> : ExpressibleByNilLiteral {
case first
case second (T)
public init(nilLiteral: ())
{
self = .first
}
}
var anEnum: AnEnum<String> = nil
var anything = anEnum
但是,这种nil
将与Optional
的nil
不同。
正如实现所建议的那样,Optional
不直接使用nil
的值,它只是将nil
文字解析为实际存储的Optional.none
。
与其他Enum
相同,Optional.none
只是一个未知值(至少你不能保证它等于任何其他值),因为没有指定rawValue
。
您的nil
将被AnEnum.first
,而Optional
的nil
将被Optional.none
。Optional.none
和AnEnum.first
是两个不同的不相关的 Enum 值。Optional
不会认为AnEnum.first
是Optional.none
,因为它们是不同的,您获得Optional.none
的唯一方法是将nil
文字分配给Optional
。
这就解释了为什么你可以将AnEnum
(您认为与 Swift 原生Optional
几乎相同)分配给Any
没有问题,但是将实际的 Swift 原生Optional
分配给Any
确实会导致问题。因为您的自定义"可选"除了对编译器的Enum
之外没有任何特殊意义。编译器只特别对待 Swift 原生Optional
,即使它在技术上只是一个Enum
。