使用隐式解包选项但测试 nil 与可选绑定是否存在技术缺点?

Is there a technical downside to using implicitly-unwrapped optionals but testing for nil vs optional binding?

好的,所以我知道在 Swift 中使用可选值的正常方法是通过可选绑定来解包它们,就像这样...

let stringA:String? = nil // (or "ABC")

if let unwrappedStringA = stringA
{
    let x:String = unwrappedStringA
}

但我也看到了以下使用隐式解包选项的方式,我个人认为它看起来更干净,因为它不仅不需要额外的变量,而且 'reads' 更好更像英文(即'If this is not nil, then...')和可读性是Swift的核心原则之一(尤其是在即将到来的Swift 3.0)。

let stringA:String! = nil // (or "ABC")

if stringA != nil
{
    let x:String = stringA
}

然而,关于后者,Swift 'purists' 将其称为 'code smell' 并坚持认为它是 'Bad, bad, bad!!'... 但他们从未解释为什么!那么...为什么这么糟糕?

注意:是的,我知道可选链接和其他不能与隐式解包可选一起使用的功能,它们都是非常酷的功能,但我特别询问针对 nil 与可选绑定测试隐式展开的可选选项的技术缺点。

我希望的是可以量化的技术原因,说明一个比另一个更好(即编译器优化、更好的安全检查、性能等),换句话说,不仅仅是一个 '嘿,因为那不是 'Swifty' 的方式!希望这是有道理的。

更新

我实际上只是找到了一种方法来解决我的一个问题(至少是表面上的),即必须创建一个新变量来保存未包装的变量。这里的技巧是因为展开的变量实际上与可选变量本身在不同的范围内,你可以使用完全相同的名称。

此外,括号内还有另一个作用域,它也可以有一个名为 stringA 的变量。这意味着您现在可以拥有三个 'stringA' 变量(但并不意味着您 应该!)...

  1. 可选
  2. 未包装的可选
  3. 括号内的新局部变量

这是显示此内容的疯狂代码...

let stringA:String? = nil // (or "ABC") // First stringA variable

if let stringA = stringA
{
    let x:String = stringA // <- This stringA is the unwrapped optional (the second stringA)

    // Now we're just getting crazy (and smelly!)
    let stringA = stringA + stringA // <- This is yet another stringA (third one!)
    let y:String = stringA 
}

再说一次,我绝不宽恕!!我只是展示了一些代码,其他人可能从有启发性的角度来看会觉得有趣。我确实做到了!

确实有一个简单的原因,这就是您列出的原因之一:"better safety checking"。可选绑定使编译器知道解包的范围,而程序员有责任跟踪您何时检查或未检查 nil.

的隐式解包可选

带有可选绑定:

let stringA:String? = nil // (or "ABC")

if let unwrappedStringA = stringA
{
    let x:String = unwrappedStringA
}

accidentallyAttemptingToUse(stringA) // Compiler error! I see my mistake.

使用隐式展开:

let stringA:String! = nil // (or "ABC")

if(stringA != nil)
{
    let x:String = stringA
}

accidentallyAttemptingToUse(stringA) // Runtime crash I might find in testing. Yuck.

IUO 有什么好处?

那么为什么要隐式解包可选值呢?它们的存在基本上是为了一个目的:肯定会有值的属性,但要在 init 之后不久才会有,因此它们必须是可选的。通常 @IBOutlet 属于这一类:你可以相信它们有一个值,并且你不想一直解包它们,但它们没有在 init 处分配,所以你必须使它们可选。 就是隐式展开的可选值的用途。

展开到同名变量

您的更新暂时建议将可选项展开为具有相同名称的变量,实际上是 excellent Swift 风格,并且很早就被 Apple 使用并经常使用。当您解包到同名变量时,代码更容易阅读,因为您可以跟踪更少(更好)的名称。绝对接受这个!

let stringA:String? = nil // (or "ABC")

if let stringA = stringA
{
    let x:String = stringA // Of *course* this is still named `stringA`;
    // it's representing the same concept, after all.
}

由于 change to implicitly unwrapped optionals in Swift 3,如果你正确地解包常规可选值而不是使用隐式解包选项,你会更快乐。

Swift 3 中,如果您将 String! 分配给新变量,则该变量的类型将为 String?。这意味着您的隐式解包可选在赋值时成为常规可选,您现在必须处理解包新变量。

此代码适用于 Swift 2.x:

let stringA: String! = "Hello"

if stringA != nil
{
    let y = stringA
    let z = y + " world!"
}

Swift 3:

let stringA: String! = "Hello"

if stringA != nil
{
    let y = stringA
    let z = y + " world!"  // ERROR: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
}

如果您改为使用 String? 并展开它,那么代码在 Swift 2.xSwift 3:

let stringA: String? = "Hello"

if let stringA = stringA
{
    let y = stringA
    let z = y + " world!"
}

在此处查看此更改的官方讨论:SE-0054: Abolish ImplicitlyUnwrappedOptional Type

本文档的动机部分指出[重点是我的]:

The ImplicitlyUnwrappedOptional ("IUO") type is a valuable tool for importing Objective-C APIs where the nullability of a parameter or return type is unspecified. It also represents a convenient mechanism for working through definite initialization problems in initializers. However, IUOs are a transitional technology; they represent an easy way to work around un-annotated APIs, or the lack of language features that could more elegantly handle certain patterns of code. As such, we would like to limit their usage moving forward, and introduce more specific language features to take their place. Except for a few specific scenarios, optionals are always the safer bet, and we’d like to encourage people to use them instead of IUOs.

This proposal seeks to limit the adoption of IUOs to places where they are actually required, and put the Swift language on the path to removing implicitly unwrapped optionals from the system entirely when other technologies render them unnecessary. It also completely abolishes any notion of IUOs below the type-checker level of the compiler, which will substantially simplify the compiler implementation.

这表明语言设计者认为您应该尽可能少地使用隐式解包可选

让我们弄清楚 IUO 的真正目的。它们最初纯粹是作为与 Objective-C 进行通信的设备。来自 Objective-C 的任何对象在理论上可能 nil,因此在开始时 每个 对象来自 Objective-C 是可选的。让每一个这样的 Optional 成为一个真正的 Optional 太具有破坏性,所以这样的每个对象都是一个隐式解包的 Optional。

然而,Apple 开始手动调整 API 以告诉 Swift 来自 Objective-C 的对象是否实际上 nil.手工调整过程即将完成 (Swift 3)。因此,来自 Objective-C 的所有内容现在要么是普通对象,要么是普通 Optional(或者,在某些情况下,您必须通过 try 才能获得的普通对象)。

既然如此,实际上 根本不需要 隐式展开可选。在这一点上,它们不过是为程序员提供的一种懒惰的便利。其他语言设备的出现使它们变得不必要。例如,用 if let 解开一个 Optional 并被迫添加一个水平花括号是很痛苦的,但现在我们有 guard let 所以你可以在没有 [=32= 的情况下解开 ] 添加一级花括号。

苹果因此开始拧紧螺丝,让IUO越来越不方便。例如,正如 vacawama 所说的那样,它们不再通过赋值进行传播。并且不再有 IUO 数组这样的东西(例如,[Int!] 不再是一种类型)。

此过程将继续进行,直到几乎没有 IUO 合法的情况。因此最好改掉使用它们的习惯现在