为什么您不能在没有警告的情况下将Optional分配给类型为Any的变量?

时间:2018-09-26 00:06:18

标签: swift optional

以下代码编译时没有警告:

版本1:

var anything: Any
anything = "woof"

可以理解... Any是任何类型,值类型或引用类型。

但是,如果我们创建一个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分配给非可选类型是一种类型不匹配。

但是,在幕后,Optionalenum,具有.none的情况,而在.some情况下,.some的情况是相关值。我的版本3使用了enumAnEnum,它也有2种情况,其中第二种情况具有关联的值。 AnEnum类型几乎与Swift本机Optional类型相同。

为什么可以为anything分配AnEnum值,但是不能为anything分配Optional值呢?

(我开始回答这个问题:Swift dictionary with mix types (optional and non-optional)

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

5 个答案:

答案 0 :(得分:6)

Optional不仅仅是一个枚举。这也是每种类型的隐式提升。 AnyOptional以奇怪的方式组合在一起,这可能令人惊讶。例如,T?AnyT??也是AnyAnyAnyAny?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

y在此处是Double?,而不是Double??,因为可选的合并。并谢天谢地。但这很奇怪。而且,如果您有涉及基于Optional的一致性的泛型,那么这可能会非常令人困惑(并且确实如此;因此产生了很多问题。...)现在用{{1}替换上面的Double },它真的可以让您快速进入盗梦空间。

因此,编译器警告您将AnyAny混合使用是一个坏主意,并且可能导致奇怪的类型结果,因此您绝对应该在执行此操作时立即将其调用。 (也许您应该停止使用Optional,因为无论如何它几乎从来都不是您真正想要的类型。)

要使其成为我能想到的最具体的示例,请考虑:

Any

您会认为这是并行的,但事实并非如此。当您将Optionals放入混音中时,您不能仅仅提取输入的类型。

顺便说一句,有趣的附录,您不能将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.) 转换为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”之间的区别?您以为可以测试一下,但我认为不能,因为所有这些都不能始终如一地工作。相反,它试图很方便。但是你不能太用力,否则所有的接缝都会露出来。

答案 1 :(得分:6)

它有自己的历史。

从此功能开始,在Swift 3时代。

SE-0116 Import Objective-C id as Swift Any type(因此,在Swift的历史中称为id-as-Any,在我看来,这是历史上最糟糕的事情。)

(直到Any才在Swift程序员中不那么流行。)

它包括一个了不起的新功能,即通用桥接转换,该功能可以保证在桥接到Objective-C时,Swift中的Any值可以转换为非空id

但是此功能导致了许多灾难性的悲剧,称为_SwiftValue

在Swift 3中首次引入的原始转换中,无论Optional<Double>还是非_SwiftValuenil都被转换为nil。在Objective-C世界中,它完全没有用,_SwiftValue上的几乎所有操作都导致崩溃,而崩溃直到运行时才发现,这抛弃了强类型语言的优点。

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

SE-0140 Warn when Optional converts to Any, and bridge Optional As Its Payload Or NSNull

自Swift 3.0.1引入此功能以来,nil被桥接到NSNull,非nil值则基于未包装的值被桥接。

此功能还包括Optional转换为Any 时的警告,以避免无意中将Optional转换为Any

您所描述的警告是从Swift历史记录开始的。


Optional的行为由许多编译器魔术(由Swift团队成员所谓)支持,例如,对于其他枚举类型,您不能使用简单的if-let ,则不能将“可选链接”与自定义枚举一起使用。 Swift编译器将Optional视为比普通枚举特殊的事物。

答案 2 :(得分:6)

很简单,这是因为Any就像是Roach Motel的Optionals。

罗奇汽车旅馆(Roach Motel)是一个蟑螂陷阱,其座右铭是:“蟑螂可以入住,但他们不可以退房。” Any和Optionals也是如此。您可以将Optional放入Any中,但是再也无法将其淘汰。

要了解我的意思,我们首先将 else 放入Any中,然后再次将其取出:

let s : String = "howdy"
let any : Any = s
let s2 = any as! String
s2 // "howdy"

现在让我们尝试使用Optional:

let s : String? = "howdy"
let any : Any = s
let s2 = any as! String? // error

糟糕!您不能将非可选的“ down”强制转换为可选,因此原来的可选会丢失。

由Optional包裹的东西没有丢失。您仍然可以打开它:

let s : String? = "howdy"
let any : Any = s
let s2 = any as! String
s2 // "howdy"

但是现在s2是一个字符串,而不是可选的。 Optional永远消失了。你不能把它弄出来。您无法确定Any 中是否包含可选内容。没了。

所以这就是为什么将Optional放入Any总是引起警告的原因。编译器说:“您可以执行此操作,但是您真的了解自己在做什么吗?”而且,如果您这样做,则有一些方法可以使警告消失(错误消息会告诉您它们是什么)。

答案 3 :(得分:0)

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

答案 4 :(得分:-1)

AnEnumOptional有何不同。

您可以通过实现Optional所做的一切来模仿Optional。但是,您创建的是另一个独立的“可选”,它不能与Swift内置Optional互操作。例如,您可以实现ExpressibleByNilLiteral,以便可以将nil分配给AnEnum,而解析为.first。就像Optionalnil解析为Optional.none

一样
enum AnEnum<T> : ExpressibleByNilLiteral {

    case first
    case second (T)

    public init(nilLiteral: ())
    {
        self = .first
    }

}

var anEnum: AnEnum<String> = nil
var anything = anEnum

但是,此nilnil的{​​{1}}不同。

根据实现建议,Optional不直接使用Optional的值,它只是将nil的文字解析为实际存储的nil

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

您的rawValue将是nil,而AnEnum.first的{​​{1}}将是OptionalnilOptional.none是两个不同的不相关枚举值。 Optional.none不会将AnEnum.first视为Optional,因为它们是不同的,而获得AnEnum.first的唯一方法是将Optional.none的文字赋值给Optional.none

这说明了为什么可以毫无问题地将实际认为是Swift本机nil的{​​{1}}分配给Optional的原因,将您认为与Swift本机AnEnum几乎相同的Optional分配给AnyOptional确实会引起问题。因为您的自定义“可选”对编译器来说除了Any以外没有其他特殊含义。即使从技术上讲,它只是Enum,编译器也只对待Swift本机Optional