【问题标题】:Why can't you assign an Optional to a variable of type `Any` without a warning?为什么不能在没有警告的情况下将 Optional 分配给“Any”类型的变量?
【发布时间】:2018-09-26 00:06:18
【问题描述】:

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

版本 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 分配给非可选类型是一种类型不匹配。

但是,在幕后,Optional 是带有 .none 大小写的 enum,以及带有 .some 大小写的 .some 大小写具有关联的值。我的版本 3 使用了 enumAnEnum,它也有 2 个案例,其中的第二个有关联的值。 AnEnum 类型与 Swift 原生的 Optional 类型几乎相同。

为什么给anything分配一个AnEnum值可以,但是给anything分配一个Optional值就不行了?

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

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

【问题讨论】:

    标签: swift optional


    【解决方案1】:

    很简单,这是因为 Any 类似于 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
    

    哎呀!您不能将非可选的“向下”强制转换为可选,因此原始的可选会丢失。

    被可选包裹的东西没有丢失了。你仍然可以打开它:

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

    但是现在s2 是一个字符串,而不是一个可选的。 Optional 已经一去不复返了。你不能把它弄出来。您无法发现放入 Any 的内容是可选的。没了。

    这就是为什么将 Optional 放入 Any 总是会引发警告的原因。编译器在说:“你可以这样做,但你真的明白你在做什么吗?”如果你这样做了,有一些方法可以使警告静音(错误消息会告诉你它们是什么)。

    【讨论】:

    • 你可以把可选的去掉,尽管编译器肯定不会让它变得容易。您必须通过例如使用通用占位符来说服它您正在转换为非可选值:gist.github.com/hamishknight/1a10df124d391cbd39a46a695dd7f8ba。然后,运行时将愉快地执行 The Right Thing™。
    • @Hamish 好的,这很有趣。
    【解决方案2】:

    它有自己的历史。

    从这个特性开始,在 Swift 3 时代。

    SE-0116 Import Objective-C id as Swift Any type(所以,在 Swift 的历史上叫id-as-Any,在我看来,这是历史上最糟糕的事情。)

    (在那之前,Any 在 Swift 程序员中并不是很流行的类型。)

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

    但是这个功能却造成了巨大的灾难性悲剧,称为_SwiftValue

    在 Swift 3 中首次引入的原始转换中,Optional&lt;Double&gt; 被转换为 _SwiftValue,无论是 nil 还是非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 中引入了此功能,nils 被桥接到 NSNull,而非nil 值将根据展开的值进行桥接。

    该功能还包括Optional 转换为Any 时发出警告,以避免无意中将Optional 误用为Any

    你所描述的警告从 Swift 历史的这个时候开始。


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

    【讨论】:

      【解决方案3】:

      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&lt;Double&gt;,而不是Double,所以as? Double 应该返回nil。但这不是因为魔法。它返回Optional(3)。坦率地说,这有点奇怪(尝试使用 MyOptional 作为枚举的版本来获得相同的东西),但如果换一种方式,那就太糟糕了。考虑:

      let data: [String: Any] = ["aDouble": aDouble]
      let y = data["aDouble"] as? Double
      

      y 在这里是 Double?,而不是 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?
      }
      

      那么我如何区分“anything is Double? but nil”与“anything is String”?你会认为你可以测试它,但我认为你不能,因为这些都不能始终如一地工作。相反,它试图方便。但是你不能太用力,否则所有的接缝都会露出来。

      【讨论】:

        【解决方案4】:

        这是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
        

        但是,这个nil 将不同于Optionalnil

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

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

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

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

        【讨论】:

          【解决方案5】:

          因为您可以通过Any? 获得Optional&lt;Any&gt;。编译器不知道您是否希望您的 anything 类型为 nilOptional.none,因此它会发出警告以确保您了解情况。

          【讨论】:

          • 但是Optional&lt;Any&gt;AnEnum&lt;Any&gt; 有什么根本不同?
          • 因为Optional&lt;Any&gt;Any? 相同,但AnEnum&lt;Any&gt; 不同。您应该问自己的真正问题是,Any?Any 有何不同? :-)
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-02-21
          • 1970-01-01
          • 1970-01-01
          • 2021-12-09
          • 1970-01-01
          • 2022-09-24
          • 2021-09-09
          相关资源
          最近更新 更多