【问题标题】:A 'Rosetta Stone' for Swift's optional types?Swift 可选类型的“罗塞塔石碑”?
【发布时间】:2014-10-02 16:44:19
【问题描述】:

我掌握(我认为)optional types in Swift 的基础知识,并且粗略了解?! 之间的区别,但我仍然对使用这些功能时得到的一些结果感到困惑——尤其是Some <T> 的角色,以及它与 <T> 本身的不同之处;我在某些情况下收到的一些特定错误消息;以及在我期望<T> 的情况下,Some <T> 似乎会弹出。

但我也觉得,即使我理解了个别案例,我对图片的把握也越来越远了,我觉得这里有一个密码,只要我完全理解,我就可以破译一个 简单的例子——罗塞塔石碑,如果你愿意的话——用于!?、可选值和解包。

例如,这里是一个简单且(我认为)详尽的基本案例目录:

class Foo {
    var one:String = "";
    var two:String?
    var three:String!
}

let test = Foo()        // {one "" nil nil}

test.one
//test.one?             //  ERROR: ? requires optional type
//test.one!             //  ERROR: ! requires optional type

// ?, unassigned
test.two                // nil
test.two?               // nil
//test.two!             // ERROR: EXEC_BAD_INSTRUCTION

test.two == nil         // true
test.two? == nil        // true
//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

//test.two.isEmpty      // ERROR: String? does not have .isEmpty
test.two?.isEmpty       // nil
//test.two!.isEmpty     // ERROR: EXEC_BAD_INSTRUCTION

// !, unassigned
test.three              // nil
test.three?             // nil
//test.three!           // ERROR: EXEC_BAD_INSTRUCTION

test.three == nil       // true
test.three? == nil      // true
//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

//test.three.isEmpty    // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty     // nil
//test.three!.isEmpty   // ERROR: EXEC_BAD_INSTRUCTION


test.two = "???"        // {one "" {Some "???"} nil}
test.three = "!!!"      // {one "" {Some "???"} three "!!!"}

// ?, assigned
test.two                // {Some "???"}
test.two?               // {Some "???"}
test.two!               // "???"

test.two == nil         // false
test.two? == nil        // false
//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

//test.two.isEmpty      // ERROR: String? does not have .isEmpty
test.two?.isEmpty       // {Some false}
test.two!.isEmpty       // false

// !, assigned
test.three              // "!!!"
test.three?             // {Some "!!!"}
test.three!             // "!!!"

test.three == nil       // false
test.three? == nil      // false
//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

test.three.isEmpty      // false
test.three?.isEmpty     // {Some false}
test.three!.isEmpty     // false

如果有人可以对此进行注释,解释每个案例的情况,我认为该答案可以作为 Swift 的这些功能如何工作的可靠参考。

【问题讨论】:

  • 注意:我知道有很多关于这个的问题(以及很多很好的答案);我都读过了。我在这里寻找的有点不同:在一个简单示例的上下文中,解释规则如何在每个特定情况下发挥作用。此外,这不是关于使用的问题。我想我明白了这些功能的原因。

标签: syntax swift semantics optional


【解决方案1】:

好的,我将尝试回答这一切。可能不需要打领带才能完成所有事情:

注意:如有错误,请随时与我联系。这需要一段时间,所以我肯定做了一些。

快速说明:Optional 实际上是一个枚举。它有两种状态:.None.Some(T),其中 T 是值的类型(在您的情况下为 String)。

test.one

Foo 有一个名为one 的属性,它返回一个String。确定的String,不是可选的,这意味着它肯定有一个值。您将其与在代码中编写 "HI!" 的方式类似。这个值真的是""

//test.one?             //  ERROR: ? requires optional type

这是一个错误,因为test.one,如上所述,返回一个明确的String,所以它不可能为零。可以保证返回值存在。

//test.one!             //  ERROR: ! requires optional type

与 ? 相同。这 !是一个 forced unwrapping 运算符,这意味着有 chance test.one 可能为 nil,但无论如何您都想强制该值(如果不是则崩溃)那里)。但是,没有可能是 nil,所以你不能有 ?或一个!。

test.two                // nil

test.two 是一个String?可以为零。因为它是可选的,所以您可以像在代码中一样返回 nil。它的真正值是.None,所以你看到的值实际上是一个字符串?不是字符串。

test.two?               // nil

这基本上和上面做的一样,只是你明确地说这个值可能是 nil。

//test.two!             // ERROR: EXEC_BAD_INSTRUCTION

您永远不能在 nil 值上使用 ! 而不期望它崩溃。当你使用这个操作符时,它会强制输出一个值(所以你会得到一个字符串,而不是字符串?)。但是,如果该值为 nil,则 没有 值可强制退出,因此您最终会导致程序崩溃。

test.two == nil         // true

test.two 显然返回 nil 或 .None(它们是等价的)。所以如果你比较nil == nil.None == .None,这是真的。

test.two? == nil        // true

同上。

//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

每次强制解包 nil 值都会导致程序崩溃。这也没有意义,因为强制展开将返回String,而不是String?String 不能是 nil

//test.two.isEmpty      // ERROR: String? does not have .isEmpty

基本上,无论何时你想调用一个可选项的方法,你都需要使用 optional-binding 或 optional-chaining(两个不同的东西)确保它有一个值。细绳?等于Optional.Some(String),你需要越过可选层才能得到你想要的字符串。

test.two?.isEmpty       // nil

这里你使用可选链。基本上,它的工作方式是评估test.two。如果值为.Some(String),那么您在字符串上调用isEmpty。否则,如果是.None,则什么也不会发生。这些可选链可以在每个语句中出现多行,例如test.two?.firstEmoji?(假设实现了这样的方法。

//test.two!.isEmpty     // ERROR: EXEC_BAD_INSTRUCTION

再一次,强制解包 nil 可选是不好的。不要在没有首先检查该值是否确实是 .Some 的情况下这样做。

test.three              // nil

由于three 被隐式解包,并且它被初始化为nil(没有被设置为其他值),这应该不足为奇。

test.three?             // nil

这不是您可能在实际代码中使用的东西,因为它本质上是可选链接,但后面没有任何东西。然而在这里,由于.three 被隐式解包? 具有“重新包装”它的效果:结果的类型现在是String?。这在这里几乎没有什么区别,但是在 test.three 分配了 String 值之后,请看下面的作用。

//test.three!           // ERROR: EXEC_BAD_INSTRUCTION

如上所述,无法解开nil。这可能看起来令人困惑,因为该声明通常被描述为产生一个“隐式展开”的变量;但这应该被理解为“隐式展开如果不是nil”。

test.three == nil       // true
test.three? == nil      // true
//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

与上面的 2 相同。如果你有一个强制解包变量,a ?似乎解开它,这是我不建议的行为。尽量不经常使用强制解包的选项,如果你真的需要的话,主要是与 UI 的某些部分有关。很多时候,它会在你意想不到的时候崩溃。

//test.three.isEmpty    // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty     // nil
//test.three!.isEmpty   // ERROR: EXEC_BAD_INSTRUCTION

当一个可选项没有被赋值时,它默认为 nil。如果你然后尝试强制解开它......我想你明白了。第一行和第三行尝试在 nil 上调用 String 的方法(在 ObjC 中工作,而不是 Swift)。第二个使用可选链接在调用方法之前检查它是否为 nil,它不能因为它知道它是 nil。

test.two = "???"        // {one "" {Some "???"} nil}
test.three = "!!!"      // {one "" {Some "???"} three "!!!"}

这将设置test.two 等于.Some("???")test.three 等于.Some("!!!") 您看到的输出只是显示了类中保存的所有变量,以及它们如何变化。

test.two                // {Some "???"}
test.two?               // {Some "???"}
test.two!               // "???"

test.two 现在是.Some("???"),所以当你调用它时,返回的是什么:一个字符串?有一个价值。当你强制解包它时,它现在返回 .Some 中保存的值而不会崩溃,因为其中确实有一个字符串。

test.two == nil         // false
test.two? == nil        // false

test.two 仍然是可选的,所以在前两个中,当将它们与 nil 进行比较时,它意识到,“嘿,有 .Some 值,所以它 not nil。”

//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

强制解包一个值会将test.two 的值从字符串中转换?到一个字符串。字符串永远不会是 nil,因为如果是,它们就必须是可选的。将绝对是 String 的值与 nil 进行比较是没有意义的,因为您知道一个事实它不是 nil;否则程序早就崩溃了!

//test.two.isEmpty      // ERROR: String? does not have .isEmpty

test.two 是字符串?,不是字符串。为了访问字符串本身,您需要使用 ?或一个!

test.two?.isEmpty       // {Some false}

这表示,“如果test.two 包含一个字符串(不是零),则查找它是否为空。”它说{Some false},因为您正在访问一个可选的成员,而不是直接的字符串。

test.two!.isEmpty       // false

!,另一方面,确实返回一个字符串。在 String 上调用 .isEmptytruefalse,在本例中为 false,因为您将其设置为非空字符串。

test.three              // "!!!"

test.three 从中强制解包字符串,在这种情况下它有效,因为它有一个值。

test.three?             // {Some "!!!"}

你把它当作一个普通的可选(不是强制解包),所以你得到一个 Some(String) 而不仅仅是一个字符串。

test.three!             // "!!!"

由于您在声明中对其进行了强制解包,因此在此处对其进行了强制解包。

test.three == nil       // false

这是另一个奇怪的行为,因为它应该是一个错误。它应该是一个字符串,不能与 nil 相提并论,但这里发生了一些古怪的事情。当我知道这件事时,我会回复你的。

test.three? == nil      // false

test.three 视为普通的可选项,并检查其状态是.None 还是nil。

//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

上面两个应该是什么样的。无法将 String 与 nil 进行比较,因此会引发错误。

test.three.isEmpty      // false

查看被强制输出的字符串值(存在;否则,它会崩溃)。字符串不为空,所以为假。

test.three?.isEmpty     // {Some false}

将其视为字符串?如果test.three 不为零,则它从 .Some(一个字符串)中获取值,并评估它是否为空,它不是。

test.three!.isEmpty     // false

String 被强制退出可选项,直接调用 isEmpty。不为空,所以返回false。

我希望我能帮助澄清事情,当我自己发现时,我会告诉你为什么会出现这种情况:]

【讨论】:

  • anOptional? 是空操作是否正确?也就是anOptional?anOptional是一样的,只有后面有.或者[...]才有效果?
  • @raxacoricofallapatorius 你能指出你正在谈论的例子吗?我不太明白你在说什么
  • 例如 test.three? 本身。这永远不会产生与test.three 不同的任何东西。
  • @raxacoricofallapatorius 在测试这条线之后,当你放一个 ?在强制解包后,它变成了一个字符串?而不是留下一个字符串!。我个人不喜欢它的工作原理,因为我认为!仅当您绝对知道该值不为零时才应使用。
  • 啊,你是对的。这根本不是无操作。但结果确实有意义:可能是String,也可能是nil 所以:String?
猜你喜欢
  • 2017-11-15
  • 2013-09-27
  • 2019-01-12
  • 2018-05-12
  • 1970-01-01
  • 2014-10-06
  • 2017-03-29
  • 1970-01-01
  • 2014-08-04
相关资源
最近更新 更多