为什么不能在没有警告的情况下为类型为"任何"的变量分配 Optional?



以下代码编译时不发出警告:

版本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 使用了一个enumAnEnum,它也有 2 个案例,其中第二个具有关联的值。AnEnum类型与 Swift 本机Optional类型几乎相同。

为什么将 AnEnum 值分配给anything没问题,但将可选值分配给anything却不行?

(我开始回答这个问题:具有混合类型(可选和非可选)的 Swift 字典)

然后意识到我真的不知道答案。

Optional

不仅仅是一个枚举。这也是每种类型的隐含促销。AnyOptional以奇怪的方式结合在一起,这可能会令人惊讶。例如,T?AnyT??也是Any.AnyAnyAny?AnyAny??也是Any.

这造成了许多令人头疼的问题,微妙的上投和下投使得人们非常不清楚Any的可选性到底是什么。例如。

var anything: Any = ""
let aDouble: Double? = 3
anything = aDouble as Any
let x = anything as? Double

这里x什么?好吧,aDoubleOptional<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替换,它很快就会出现在你身上。

因此,编译器警告您,将AnyOptional混合是一个坏主意,可能会导致奇怪的类型结果,并且您绝对应该在任何时候调用它。(也许你应该停止使用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?
}

那么我如何区分"anythingDouble?但为零"与"anythingString"之间的区别呢?你会认为你可以测试它,但我认为你不能,因为这些都不能始终如一地工作。相反,它试图方便。但是你不能用力过猛,否则所有的接缝都会显露出来。

很简单,这是因为 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还是非nilOptional<Double>都转换为_SwiftValue。它在 Objective-C 世界中是完全没用的,几乎所有对_SwiftValue的操作都会导致崩溃,直到运行时才能发现,丢弃了强类型语言的优点。

因此,Swift 团队需要立即引入一项新功能:

SE-0140 当可选转换为任意时发出警告,并将可选桥接为其有效负载或 NSNull

自从 Swift 3.0.1 中引入此功能以来,nil被桥接到NSNull值,非nil值被桥接基于解开的值进行桥接。

该功能还包括Optional转换为Any时的警告,以避免无意中滥用OptionalAny

您描述的警告在 Swift 历史记录中的这个时候开始。


Optional的行为得到了许多编译器魔术(Swift 团队成员所谓的)的支持,例如,你不能对其他枚举类型使用简单的 if-let,你不能对你的自定义枚举使用可选链接。Swift 编译器将Optional视为比普通枚举更特殊的东西。

因为你可以有一个Optional<Any>,通过做Any?。编译器不知道您期望anything类型是nil还是Optional.none因此它会发出警告以确保您了解情况。

以下是AnEnumOptional的不同之处。

您可以通过实现Optional所做的一切来模仿Optional。但是,您创建的是另一个独立的"可选",无法与 Swift 内置Optional互操作。例如,您可以实现ExpressibleByNilLiteral以便将nil分配给解析为.firstAnEnum。就像Optional如何nilOptional.none

enum AnEnum<T> : ExpressibleByNilLiteral {
case first
case second (T)
public init(nilLiteral: ())
{
self = .first
}
}
var anEnum: AnEnum<String> = nil
var anything = anEnum

但是,这种nil将与Optionalnil不同。

正如实现所建议的那样,Optional不直接使用nil的值,它只是将nil文字解析为实际存储的Optional.none

与其他Enum相同,Optional.none只是一个未知值(至少你不能保证它等于任何其他值),因为没有指定rawValue

您的nil将被AnEnum.first,而Optionalnil将被Optional.noneOptional.noneAnEnum.first是两个不同的不相关的 Enum 值。Optional不会认为AnEnum.firstOptional.none,因为它们是不同的,您获得Optional.none的唯一方法是将nil文字分配给Optional

这就解释了为什么你可以将AnEnum(您认为与 Swift 原生Optional几乎相同)分配给Any没有问题,但是将实际的 Swift 原生Optional分配给Any确实会导致问题。因为您的自定义"可选"除了对编译器的Enum之外没有任何特殊意义。编译器只特别对待 Swift 原生Optional,即使它在技术上只是一个Enum

最新更新