Swift语言中的感叹号是什么意思?

Tro*_*roy 513 optional swift forced-unwrapping

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)
Run Code Online (Sandbox Code Playgroud)

然后,当将公寓分配给该人时,他们使用感叹号来"解开实例":

john!.apartment = number73
Run Code Online (Sandbox Code Playgroud)

"打开实例"是什么意思?为什么有必要?它与仅执行以下操作有何不同:

john.apartment = number73
Run Code Online (Sandbox Code Playgroud)

我对Swift语言很新.只是试图让基础知识失败.


更新:
我缺少的这个难题的大部分(在答案中没有直接说明 - 至少在撰写本文时没有说明)是当你做以下事情时:

var john: Person?
Run Code Online (Sandbox Code Playgroud)

正如我原先想的那样,这并不意味着" john属于类型Person而且可能是零".我只是误解了这一点,Person而且Person?是完全不同的类型.一旦我掌握了这一点,所有其他的?,!疯狂的,以及下面的重大答案,都变得更有意义.

Ash*_*ley 525

"打开实例"是什么意思?为什么有必要?

据我所知(这对我来说也很新)......

术语"包裹"意味着我们应该将一个可选变量视为礼物,用闪亮的纸包裹,可能(可悲的是)是空的.

当"换行"时,Optional变量的值是一个带有两个可能值的枚举(有点像布尔值).此枚举描述变量是保持值(Some(T))还是不保存(None).

如果有值,可以通过"展开"变量(获取Tfrom Some(T))来获得.

有什么john!.apartment = number73不同john.apartment = number73?(意译)

如果你写了一个Optional变量的名字(例如文本john,没有!),这就是"包装"枚举(Some/None),而不是值本身(T).所以john不是一个实例Person,它没有apartment成员:

john.apartment
// 'Person?' does not have a member named 'apartment'
Run Code Online (Sandbox Code Playgroud)

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

  • "强制解包":( 如果存在则john!给出Person值,如果为零则给出运行时错误)
  • "可选绑定":( 如果值存在则if let p = john { println(p) }执行println)
  • "可选链接":( john?.learnAboutSwift()如果值存在,则执行此制作方法)

我猜你会选择其中一种解包方式,具体取决于nil情况应该发生什么,以及它有多大可能.这种语言设计强制明确处理nil case,我想这可以提高Obj-C的安全性(很容易忘记处理nil的情况).

更新:

感叹号也用于声明"Implicitly Unwrapped Optionals"的语法中.

在到目前为止的示例中,john变量已声明为var john:Person?,并且它是可选的.如果您想要该变量的实际值,则必须使用上述三种方法之一将其展开.

如果它被声明为var john:Person!,则该变量将是一个隐式解包可选(请参阅Apple书中带有此标题的部分).访问该值时无需打开此类变量,john无需其他语法即可使用.但苹果公司的书说:

当变量有可能在以后变为零时,不应使用隐式展开的选项.如果需要在变量的生命周期内检查nil值,请始终使用普通的可选类型.

更新2:

Mike Ash 的文章" 有趣的快速特征 "为可选类型提供了一些动力.我认为这很棒,写得很清楚.

更新3:

另一篇关于感叹号隐式解包的可选用法的文章:Chris Adamson的" Swift and the Last Mile ".文章解释说,这是Apple用来声明其Objective-C框架使用的类型的实用指标,这些框架可能包含nil.将类型声明为可选(使用?)或隐式展开(使用!)是"安全和便利之间的权衡".在本文给出的示例中,Apple选择将类型声明为隐式展开,使调用代码更方便,但安全性更低.

也许苹果可能在未来梳理他们的框架,消除隐含地解开("可能永远不会")参数的不确定性,并用可选的替换它们("肯定可能是零[希望,有记录!]情况")或标准非-optional("永远不会是")声明,基于其Objective-C代码的确切行为.

  • 嗨@RichardWashington - 我添加了一个更新我的答案,希望澄清一些. (2认同)

Amr*_*Amr 128

以下是我认为的不同之处:

var john: Person?
Run Code Online (Sandbox Code Playgroud)

意味着约翰可以是零

john?.apartment = number73
Run Code Online (Sandbox Code Playgroud)

编译器会将此行解释为:

if john != nil {
    john.apartment = number73
}
Run Code Online (Sandbox Code Playgroud)

john!.apartment = number73
Run Code Online (Sandbox Code Playgroud)

编译器将简单地解释这一行:

john.apartment = number73
Run Code Online (Sandbox Code Playgroud)

因此,使用!将解包if语句,并使其运行得更快,但如果john为nil,则会发生运行时错误.

所以这里的包装并不意味着它是内存包装,但它意味着它是代码包装的,在这种情况下它用if语句包装,并且因为Apple在运行时密切关注性能,他们想给你一个方法让您的应用以最佳性能运行.

更新:

4年后回到这个答案,因为我在Stackoverflow中获得了最高的声誉:)我误解了当时解包的意义.现在4年后,我相信在这里展开的意思是将代码从原始的紧凑形式扩展.此外,它意味着消除该对象周围的模糊性,因为我们不确定它的定义是否为零.就像上面Ashley的回答一样,把它想象成一个可以包含任何内容的礼物.但我仍然认为解包是代码解包而不是基于内存的解包使用枚举.

  • 在我的游乐场john.apartment = number73不编译,你必须指定john?.apartment = number73 (9认同)
  • 展开与wrt性能无关."nil check"仍然必须在运行时完成,唯一的区别是如果Optional中没有值,则会抛出运行时错误. (4认同)
  • @ChuckPinkert是对的,第4行应编辑为john?.apartment = number73,但是答案很好! (2认同)

Ale*_*sco 65

TL; DR

Swift语言中的感叹号是什么意思?

感叹号有效地说:"我知道这个选项肯定有价值; 请使用它."这被称为强制解包可选的值:

let possibleString: String? = "An optional string."
print(possibleString!) // requires an exclamation mark to access its value
// prints "An optional string."

let assumedString: String! = "An implicitly unwrapped optional string."
print(assumedString)  // no exclamation mark is needed to access its value
// prints "An implicitly unwrapped optional string."
Run Code Online (Sandbox Code Playgroud)

资料来源:https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_399

  • 你的答案很棒,因为我理解_what_正在进行中.我不明白的是为什么存在隐式解包的选项.为什么创建定义为隐式解包的可选String而不是常规String类型的东西?之后它们的用法是相同的.我错过了什么? (10认同)

Ben*_*ieb 37

如果john是一个可选的var(由此声明)

var john: Person?
Run Code Online (Sandbox Code Playgroud)

那么约翰就有可能没有价值(用ObjC的说法,零价值)

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

if let otherPerson = john {
    otherPerson.apartment = number73
}
Run Code Online (Sandbox Code Playgroud)

这个内部只会评估john是否有值.

  • 感谢您的回复,我现在明白了*感叹号上写着什么,我仍然试图绕过*为什么*...你说它告诉编译器"我知道这有价值,你不要需要测试它".当编译器没有测试它时它会给我带来什么?没有感叹号,编译器是否会抛出错误(我没有环境,但是,我可以测试Swift)?是个 !所有可选变量都需要100%?如果是这样,为什么苹果会为此烦恼,而不是仅仅因为它缺乏*!意思是"我知道这有价值,你不需要测试它"? (4认同)
  • @RichardWashington你很有理由感到困惑!文档中的示例会遗漏一些内容,并且会产生误导,因为他们所做的只是使用println.显然,至少有几个不需要打开选项的情况.一个是使用println时.另一种是使用字符串插值.那么,也许println和字符串插值在封面下展开?也许有人对此有更多的了解.看看Xcode6 Beta标题定义并未向我揭示有关此魔法的任何信息. (3认同)

Pau*_*ell 27

一些大局观透视添加到其他有用但更详细的中心答案:

在Swift中,感叹号出现在几个上下文中:

  • 强行打开: let name = nameLabel!.text
  • 隐含未解包的期权: var logo: UIImageView!
  • 强制铸造: logo.image = thing as! UIImage
  • 未处理的例外情况: try! NSJSONSerialization.JSONObjectWithData(data, [])

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

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

当您使用!的斯威夫特,你基本上是说:"嘿,编译器,我知道你认为错误可以在这里发生,但我知道,总的确定性,它永远不会."

并非所有有效代码都适合Swift的编译时类型系统 - 或者任何语言的静态类型检查.在某些情况下,您可以逻辑地证明错误永远不会发生,但您无法向编译器证明.这就是为什么Swift的设计师首先添加了这些功能.

但是,无论何时使用!,您都要排除错误的恢复路径,这意味着......

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

感叹号也说,"嘿斯威夫特,我这样一定能永远不会发生这个错误,它是更好地为您崩溃我的整个应用程序比它是对我为它编写一个恢复路径."

这是一个危险的断言.它可能是正确的:在任务关键代码中,您已经仔细考虑过代码的不变量,可能是虚假输出比崩溃更糟糕.

然而,当我!在野外看到时,它很少被如此谨慎地使用.相反,它经常意味着,"这个值是可选的,我并没有真正想到为什么它可能是零或如何正确处理这种情况,但添加!使它编译...所以我的代码是正确的,对吧?"

小心感叹号的傲慢.代替…

3.最好谨慎使用感叹号.

这些!构造中的每一个都有一个?强制您处理error/nil情况的对应物:

  • 有条件的解包: if let name = nameLabel?.text { ... }
  • 选配: var logo: UIImageView?
  • 有条件的演员表: logo.image = thing as? UIImage
  • 无失败例外情况: try? NSJSONSerialization.JSONObjectWithData(data, [])

如果您想要使用!,请务必仔细考虑您不使用的原因?.如果!操作失败,崩溃你的程序真的是最好的选择吗?为什么这个值是可选的/可用的?

您的代码在nil/error情况下是否有合理的恢复路径?如果是这样,请编码.

如果它不可能是nil,如果错误永远不会发生,那么有没有合理的方法来重写你的逻辑,以便编译器知道?如果是这样,那就去做; 您的代码不易出错.

有时候没有合理的方法来处理错误,而只是忽略错误 - 从而进行错误的数据 - 会比崩溃更糟糕.那些是使用武力展开的时代.

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

这并不是说你永远不应该!在你的代码中使用- 只是你应该谨慎使用它,而不是将它作为默认选项.


Fry*_*Fry 24

john是可选的var.所以可以包含一个nil值.要确保该值不是nil !,请在var名称末尾使用a .

从文档

"一旦确定可选项包含值,就可以通过在可选项名称的末尾添加感叹号(!)来访问其基础值.感叹号有效地说:"我知道这个选项肯定有价值; 请用它."

另一种检查非零值的方法是

    if let j = json {
        // do something with j
    }
Run Code Online (Sandbox Code Playgroud)

  • 是的确如此,但重点是默认情况下Swift尝试在编译时捕获错误.如果您知道或认为您知道此变量不能包含nil,则可以使用感叹号删除该检查. (6认同)
  • 是的,我已经在文档中看到了这一点,但它似乎仍然没有必要......因为对我来说,`john.apartment = number73`也说“我知道这个可选肯定有一个值;请使用它。”...... 。 (2认同)

Ram*_*ala 16

这里有些例子:

var name:String = "Hello World"
var word:String?
Run Code Online (Sandbox Code Playgroud)

哪里word是可选值.表示它可能包含也可能不包含值.

word = name 
Run Code Online (Sandbox Code Playgroud)

这里name有一个值,所以我们可以分配它

var cow:String = nil
var dog:String!
Run Code Online (Sandbox Code Playgroud)

dog被强行拆开意味着它必须包含值

dog = cow
Run Code Online (Sandbox Code Playgroud)

应用程序将崩溃,因为我们将分配nil给unwrapped


小智 15

在这种情况下...

var John:人!

这意味着,最初John将拥有零值,它将被设置并且一旦设置将永远不会被nil-led引导.因此,为方便起见,我可以使用更简单的语法来访问可选的var,因为这是一个"隐式解包的可选"


Jim*_*oll 5

如果您来自 C 系列语言,您会考虑“指向 X 类型对象的指针,这可能是内存地址 0 (NULL)”,如果您来自动态类型语言,您将思考“可能是 X 类型但可能是未定义类型的对象”。这两个实际上都不正确,尽管以迂回的方式第一个接近。

您应该考虑它的方式就像它是一个对象,如:

struct Optional<T> {
   var isNil:Boolean
   var realObject:T
}
Run Code Online (Sandbox Code Playgroud)

当您使用foo == nil它来测试您的可选值时,它确实返回foo.isNil,而当您说它foo!返回foo.realObject带有foo.isNil == false. 请务必注意这一点,因为如果foo您执行时实际上为 nil foo!,则这是一个运行时错误,因此通常您希望使用条件 let 来代替,除非您非常确定该值不会为 nil。这种诡计意味着语言可以是强类型的,而不必强迫您测试值是否无处不在。

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

在其他情况下,您会在类型上看到感叹号,例如:

func foo(bar: String!) {
    print(bar)
}
Run Code Online (Sandbox Code Playgroud)

这大致相当于通过强制解包接受一个可选项,即:

func foo(bar: String?) {
    print(bar!)
}
Run Code Online (Sandbox Code Playgroud)

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