Swift编程语言指南中有如下示例:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

var john: Person?
var number73: Apartment?

john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

//From Apple's “The Swift Programming Language” guide (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)

然后,在给这个人分配公寓时,他们使用感叹号来“打开实例”:

john!.apartment = number73

“展开实例”是什么意思?为什么有必要?它与仅仅做以下事情有什么不同:

john.apartment = number73

我对Swift语言很不熟悉。我只是想把基本的东西记下来。

更新: 我遗漏了一个很大的谜团(答案中没有直接说明——至少在写这篇文章的时候没有),那就是当你做以下事情的时候:

var john: Person?

这并不意味着“约翰是人的类型,它可能是nil”,因为我最初的想法。我只是误解了《人与人》是完全不同的类型。一旦我明白了,所有其他的?,!疯狂,以及下面这些精彩的回答,让我觉得更有道理。


当前回答

John是一个可选的Person,这意味着它可以保存值或为nil。

john.apartment = number73

如果John不是可选选项,则使用。由于john从不为nil,我们可以确定它不会在nil值时调用apartment。而

john!.apartment = number73

向编译器承诺John不是nil,然后打开可选选项以获取John的值并访问John的公寓属性。如果你知道john不是nil就用这个。如果你在可选的nil上调用这个,你会得到一个运行时错误。

文档中包含了一个很好的示例,其中convertedNumber是可选的。

if convertedNumber {
    println("\(possibleNumber) has an integer value of \(convertedNumber!)")
} else {
    println("\(possibleNumber) could not be converted to an integer")
}

其他回答

如果john是一个可选的变量(这样声明)

var john: Person?

那么john可能没有值(在ObjC中为nil值)

感叹号基本上告诉编译器“我知道这有一个值,你不需要测试它”。如果你不想使用它,你可以有条件地测试它:

if let otherPerson = john {
    otherPerson.apartment = number73
}

只有当john有值时,这个内部才有价值。

John是一个可选的变量,它可以包含一个nil值。要确保该值不是nil,请使用a !在var名称的末尾。

从文档

一旦您确定可选选项确实包含一个值,您可以通过在可选选项名称的末尾添加一个感叹号(!)来访问它的底层值。感叹号有效地表示,“我知道这个可选选项肯定有价值;请使用它。”

另一种检查非nil值的方法是(可选的展开)

    if let j = json {
        // do something with j
    }

一些大局观的观点可以补充到其他有用但更注重细节的答案中:

在Swift中,感叹号出现在几种情况下:

强制展开:let name = nameLabel!.text 隐式打开可选项:var logo: UIImageView! 强制铸造:标志。图像=事物!用户界面图像 未处理的异常:试试!NSJSONSerialization。JSONObjectWithData(数据,[])

它们中的每一个都是不同的语言结构,具有不同的含义,但它们都有三个重要的共同点:

1. 感叹号绕过了Swift的编译时安全检查。

当你使用!在Swift中,你实际上是在说:“嘿,编译器,我知道你认为这里可能会发生错误,但我完全确定它永远不会发生。”

并不是所有有效的代码都符合Swift的编译时类型系统,也不是任何语言的静态类型检查。有些情况下,你可以从逻辑上证明错误永远不会发生,但你不能向编译器证明它。这就是为什么Swift的设计师一开始就添加了这些功能。

然而,无论何时使用!,您都排除了为错误提供恢复路径,这意味着……

2. 感叹号是潜在的崩溃。

感叹号还表示,“嘿,斯威夫特,我非常确定这个错误永远不会发生,所以你最好让我的整个应用程序崩溃,而不是让我为它编写恢复路径。”

这是一个危险的论断。它可能是正确的:在任务关键型代码中,您已经认真考虑了代码的不变量,伪输出可能比崩溃更糟糕。

然而,当我看到!在野外,很少有人如此谨慎地使用它。相反,它通常意味着,“这个值是可选的,我并没有真正认真思考为什么它可以为nil或如何正确处理这种情况,但添加!让它编译……所以我的代码是正确的,对吧?”

小心感叹号的傲慢。而不是……

3.感叹号最好少用。

每一个!构念有?强制你处理错误/nil情况的对应项:

条件解包装:if let name = nameLabel?文本{…} 可选:var logo: UIImageView? 条件强制转换:标志。Image = thing as?用户界面图像 失败时为空异常:试试?NSJSONSerialization。JSONObjectWithData(数据,[])

如果你想要使用!,最好仔细考虑一下为什么你不使用!代替。使程序崩溃真的是最好的选择吗?操作失败?为什么这个值是可选的/可失败的?

是否有一个合理的恢复路径,你的代码可以采取在nil/错误的情况下?如果是,编码它。

如果它不可能是nil,如果错误永远不会发生,那么有没有一种合理的方法来重做你的逻辑,让编译器知道?如果是,那就去做;您的代码将更不容易出错。

有时没有合理的方法来处理错误,简单地忽略错误——从而处理错误的数据——会比崩溃更糟糕。这些时候就应该使用强制拆包。

我定期搜索我的整个代码库!并审查它的每一次使用。很少有使用方法经得起推敲。(在撰写本文时,整个Siesta框架只有两个实例。)

这并不是说你不应该使用!在你的代码中——只是你应该谨慎地使用它,永远不要让它成为默认选项。

总之(!): 在你声明了一个变量并且你确定这个变量有一个值之后。

let assumedString: String! = "Some message..."
let implicitString: String = assumedString

否则你必须在每次传递值之后都这样做…

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark

“展开实例”是什么意思?为什么有必要?

据我所知(这对我来说也很陌生)……

术语“包装”意味着我们应该把一个可选变量想象成一个礼物,包裹在闪亮的纸里,可能是空的(很遗憾!)。

当“包装”时,可选变量的值是一个包含两个可能值的enum(有点像布尔值)。这个枚举描述了变量是否包含一个值(Some(T)),或者是否包含一个值(None)。

如果有值,则可以通过“展开”变量来获得(从Some(T)中获取T)。

约翰好吗?73号公寓和约翰不一样。公寓= 73号?(转述)

如果你写一个可选变量的名字(例如文本john,不带!),这指的是“包装”enum (Some/None),而不是值本身(T)。所以john不是Person的实例,它没有公寓成员:

john.apartment
// 'Person?' does not have a member named 'apartment'

实际的Person值可以通过多种方式展开:

“强制拆包”:约翰!(如果Person值存在,则给出Person值,如果为nil则出现运行时错误) "optional binding": if let p = John {println(p)}(如果值存在则执行println) "optional chaining": john?.learnAboutSwift()(如果值存在,则执行这个创建的方法)

我猜你会选择其中一种展开方式,这取决于在nil情况下会发生什么,以及它的可能性有多大。这种语言设计强制显式地处理nil情况,我认为这比Obj-C提高了安全性(在Obj-C中很容易忘记处理nil情况)。

更新:

感叹号也用于声明“隐式未包装的可选选项”的语法中。

在到目前为止的例子中,john变量被声明为var john:Person?,它是可选的。如果需要该变量的实际值,则必须使用上述三种方法之一展开它。

如果它被声明为var john:Person!相反,该变量将是一个隐式未包装的可选变量(参见Apple book中带有此标题的部分)。在访问值时不需要展开这类变量,并且可以在不使用其他语法的情况下使用john。但苹果的书说:

当变量有可能在稍后变为nil时,不应该使用隐式打开的可选选项。如果您需要在变量的生命周期内检查nil值,请始终使用正常的可选类型。

更新2:

Mike Ash的文章“有趣的Swift特性”为可选类型提供了一些动力。我觉得写得很好,很清楚。

更新3:

Another useful article about the implicitly unwrapped optional use for the exclamation mark: "Swift and the Last Mile" by Chris Adamson. The article explains that this is a pragmatic measure by Apple used to declare the types used by their Objective-C frameworks which might contain nil. Declaring a type as optional (using ?) or implicitly unwrapped (using !) is "a tradeoff between safety and convenience". In the examples given in the article, Apple have chosen to declare the types as implicitly unwrapped, making the calling code more convenient, but less safe.

也许Apple会在未来梳理他们的框架,删除隐式展开参数(“可能永远不会为nil”)的不确定性,并将它们替换为可选的(“当然可能是nil,特别是[希望有文档!)或标准的非可选(“永不为空”)声明,这取决于它们的Objective-C代码的确切行为。