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”,因为我最初的想法。我只是误解了《人与人》是完全不同的类型。一旦我明白了,所有其他的?,!疯狂,以及下面这些精彩的回答,让我觉得更有道理。


当前回答

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

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

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

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

其他回答

如果你来自c语言家族,你会想“指向X类型对象的指针,它可能是内存地址0 (NULL)”,如果你来自动态类型语言,你会想“对象可能是X类型,但可能是未定义的类型”。这两种说法实际上都不正确,尽管迂回地说,第一个说法很接近。

你应该把它想象成一个物体:

struct Optional<T> {
   var isNil:Boolean
   var realObject:T
}

When you're testing your optional value with foo == nil it's really returning foo.isNil, and when you say foo! it's returning foo.realObject with an assertion that foo.isNil == false. It's important to note this because if foo actually is nil when you do foo!, that's a runtime error, so typically you'd want to use a conditional let instead unless you are very sure that the value will not be nil. This kind of trickery means that the language can be strongly typed without forcing you to test if values are nil everywhere.

实际上,它并不是这样做的,因为工作是由编译器完成的。在高层次上有一个类型Foo?它与Foo是分开的,这可以防止接受类型Foo的函数函数接收到nil值,但在低级别上,可选值不是一个true对象,因为它没有属性或方法;实际上,它很可能是一个指针,在强制展开时,通过适当的测试,它可能是NULL(0)。

还有一种情况,你会看到感叹号是在一种类型上,比如:

func foo(bar: String!) {
    print(bar)
}

这大致相当于接受一个强制展开的可选选项,即:

func foo(bar: String?) {
    print(bar!)
}

你可以使用它来创建一个方法,该方法在技术上接受一个可选值,但如果它为nil,则会出现运行时错误。在当前版本的Swift中,这显然绕过了is-not-nil断言,所以你会有一个低级错误。通常不是个好主意,但在从另一种语言转换代码时可能很有用。

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

在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框架只有两个实例。)

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

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:人!

它意味着,最初John的值为nil,它会被设置,一旦设置,就不会再以nil为先导。因此,为了方便起见,我可以使用更简单的语法来访问可选变量因为这是一个隐式展开的可选变量

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

从文档

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

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

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