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

时间:2014-06-03 14:52:27

标签: swift optional forced-unwrapping

The Swift Programming Language guide有以下示例:

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?

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

23 个答案:

答案 0 :(得分:515)

  

“解包实例”是什么意思?为什么有必要?

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

术语“包裹”意味着我们应该将一个可选变量视为礼物,包裹在闪亮的纸张中,可能(遗憾地!)为空

当“换行”时,Optional变量的值是一个带有两个可能值的枚举(有点像布尔值)。此枚举描述变量是否包含值(Some(T))或不包含(None)。

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

  

john!.apartment = number73john.apartment = number73的区别如何? (意译)

如果你写了一个Optional变量的名字(例如文本john,没有!),这就是指“包裹”枚举(Some / None),而不是值本身(T )。因此john不是Person的实例,并且没有apartment成员:

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

实际的Person值可以通过各种方式解包:

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

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

<强>更新

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

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

如果它被声明为var john:Person!,则该变量将是一个隐式展开的可选项(请参阅Apple书中带有此标题的部分)。访问该值时无需打开此类变量,john可以在没有其他语法的情况下使用。但苹果的书说:

  

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

更新2

Mike Ash的文章“Interesting Swift Features”为可选类型提供了一些动力。我认为这很好,写得很清楚。

更新3

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

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

答案 1 :(得分:127)

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

var john: Person?

意味着约翰可以是零

john?.apartment = number73

编译器会将此行解释为:

if john != nil {
    john.apartment = number73
}

虽然

john!.apartment = number73

编译器会将此行解释为:

john.apartment = number73

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

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

<强>更新

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

答案 2 :(得分: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."

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

答案 3 :(得分:37)

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

var john: Person?

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

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

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

此内部仅评估john是否有值。

答案 4 :(得分:27)

将一些重要的视角添加到其他有用但更详细的答案中:

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

  • 强制展开:let name = nameLabel!.text
  • 隐式解包的选项:var logo: UIImageView!
  • 强迫施法:logo.image = thing as! UIImage
  • 未处理的例外:try! NSJSONSerialization.JSONObjectWithData(data, [])

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

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

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

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

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

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

一个感叹号还说,“嘿Swift,我所以肯定这个错误永远不会发生,因为你崩溃我的整个应用程序比你更好让我为它编写恢复路径。“

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

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

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

3。感叹号最好用得很少。

这些!构造中的每一个都有一个?对应物,迫使您处理错误/零个案:

  • 有条件展开:if let name = nameLabel?.text { ... }
  • 选项:var logo: UIImageView?
  • 有条件演员:logo.image = thing as? UIImage
  • 无失败例外:try? NSJSONSerialization.JSONObjectWithData(data, [])

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

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

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

有时候没有合理的方法来处理错误,而忽略错误 - 从而导致错误的数据 - 会比崩溃更糟糕。 那些是使用强制解包的时候。

我会定期搜索我的整个代码库!并审核每次使用它。很少有用法经得起审查。 (截至撰写本文时,整个Siesta框架完全具有two instances。)

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

答案 5 :(得分:24)

john是可选的var。因此可以包含nil值。要确保该值不为零,请使用!名称末尾的var

来自文档

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

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

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

答案 6 :(得分:16)

以下是一些例子:

word

其中word = name 是可选值。意味着它可能包含也可能不包含值。

name

此处var cow:String = nil var dog:String! 有一个值,因此我们可以指定它

dog

强行打开dog = cow 意味着它必须包含值

nil

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

答案 7 :(得分:15)

在这种情况下......

var John:人!

这意味着,最初约翰将拥有零值,它将被设置并且一旦设置将永远不再为零。因此,为方便起见,我可以使用更简单的语法来访问可选的var,因为这是一个&#34;隐式解包的可选&#34;

答案 8 :(得分:4)

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

你应该如何思考它就像是一个像这样的对象:

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

当您使用foo == nil测试可选值时,它确实正在返回foo.isNil,当您说foo!时,它返回foo.realObject并声明foo.isNil == false }。重要的是要注意这一点,因为当foo执行时foo!实际上是nil时,这是一个运行时错误,所以通常你会想要使用条件let,除非你非常确定该值是不是零。这种诡计意味着语言可以强类型,而不必强迫您测试值是否为零。

实际上,它并不是真的那样,因为工作是由编译器完成的。在较高级别,类型Foo?Foo分开,并且可以阻止接受类型Foo的func接收nil值,但是在低级别,可选值为n不是真正的对象,因为它没有属性或方法;它实际上可能是一个指针,当强制解包时,它可以通过NULL(0)进行适当的测试。

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

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

这大致相当于接受强制解包的可选项,即:

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

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

答案 9 :(得分:3)

!意味着你强行打开物体!如下。更多信息可以在Apples文档中找到,可以在这里找到:https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/TheBasics.html

答案 10 :(得分:3)

如果您熟悉C#,这就像Nullable类型,它们也使用问号声明:

Person? thisPerson;

这种情况下的感叹号等同于访问可空类型的.Value属性,如下所示:

thisPerson.Value

答案 11 :(得分:2)

在目标C中,没有值的变量等于'nil'(也可以使用与0和false相同的'nil'值),因此可以在条件语句中使用变量(具有值的变量是相同的)为'TRUE'而没有值的那些等于'FALSE')。

Swift通过提供“可选值”来提供类型安全性。即它可以防止因分配不同类型的变量而形成的错误。

所以在Swift中,只能在条件语句中提供布尔值。

var hw = "Hello World"

这里,虽然'hw'是一个字符串,但它不能像在目标C中那样用在if语句中。

//This is an error

if hw

 {..}

为此,需要将其创建为,

var nhw : String? = "Hello World"

//This is correct

if nhw

 {..}

答案 12 :(得分:2)

!在一个对象的末尾表示该对象是可选的,并且如果否则返回nil则解包。这通常用于捕获可能导致程序崩溃的错误。

答案 13 :(得分:2)

简短(!): 声明变量并确定变量保持值后。

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

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

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

答案 14 :(得分:1)

整个故事的开头是一个叫做可选变量的快速特征。 这些是可能具有值或可能没有值的变量。一般来说,swift不允许我们使用未初始化的变量,因为这可能导致崩溃或意外原因,并且还为后门服务器占位符。 因此,为了声明一个最初未确定其值的变量,我们使用'?'。 当声明这样的变量时,要将其用作某个表达式的一部分,必须在使用前解包它们,解包是一个操作,通过该操作可以发现变量的值,这适用于对象。如果您尝试使用它们而不解包,则会出现编译时错误。 要打开一个可变var变量,感叹号“!”用来。

现在有些时候你知道这样的可选变量会被系统分配值,例如你自己的程序,但稍后某些时候,例如UI出口,在这种情况下,而不是使用问号“?”声明一个可选变量? “我们用“!”。

因此系统知道这个用“!”声明的变量现在是可选的,没有任何价值,但会在其生命周期的后期收到一个值。

因此感叹号有两种不同的用法, 1.声明一个可选的变量,稍后肯定会收到值 2.在表达式中使用可选变量之前将其展开。

以上描述避免了太多技术性的东西,我希望。

答案 15 :(得分:1)

John是一个可选的Person,意思是它可以保存一个值或者为零。

john.apartment = number73
如果john不是可选的,则使用

。由于约翰永远不会是零,我们可以肯定它不会以零值呼叫公寓。而

john!.apartment = number73

承诺编译器john不是nil然后打开可选项以获取john的值并访问john的公寓属性。如果你知道约翰不是零,请使用此选项。如果你在一个nil可选中调用它,你将会遇到运行时错误。

该文档包含一个很好的例子,用于使用convertNumber是可选的。

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

答案 16 :(得分:1)

如果您将它作为可选项使用,它会打开可选项并查看是否存在某些内容。如果在if-else语句中使用它,则为NOT代码。例如,

if (myNumber != 3){
 // if myNumber is NOT 3 do whatever is inside these brackets.
)

答案 17 :(得分:1)

可选变量可能包含值或可能不是

案例1:var myVar:String? = "Something"

案例2:var myVar:String? = nil

现在,如果你问myVar !,你告诉编译器在案例1中返回一个值,它将返回"Something"

在案例2中它会崩溃。

意思! mark会强制编译器返回一个值,即使它不在那里。这就是为什么名称强制展开

答案 18 :(得分:1)

简单的单词

使用感叹号表示变量必须包含非零值(永远不会为零)

答案 19 :(得分:1)

简单地说,感叹号意味着可选的包装被打开。可选是一个可以有值的变量 - 所以你可以使用if let语句as shown here来检查变量是否为空,然后强行打开它。如果你强行打开一个空的可选项,你的程序会崩溃,所以要小心!通过在变量的显式赋值的末尾添加一个问号来声明Optionals,例如我可以写:

var optionalExample: String?

此变量没有值。如果我打开它,程序会崩溃,Xcode会告诉你你试图打开一个值为nil的可选项。

希望有所帮助。

答案 20 :(得分:0)

Simple the Optional variable allows nil to be stored.

var str : String? = nil

str = "Data"

To convert Optional to the Specific DataType, We unwrap the variable using the keyword "!"

func get(message : String){
   return
}

get(message : str!)  // Unwapped to pass as String

答案 21 :(得分:0)

对于Google员工:

john!.department

...告诉编译器:

  • 我知道john是可选的
  • 将其当作有价值的东西使用
  • 如果没有的话就崩溃

在生产中,使用guard letif let处理无价值和无效硬崩溃的情况。

答案 22 :(得分:-1)

问自己

  • 类型 person? 是否有apartment成员/财产? OR
  • 类型 person 是否有apartment会员/财产?

如果您无法回答此问题,请继续阅读:

要了解您可能需要对泛型的超级基本理解。见here。 Swift中的很多东西都是用泛型编写的。选项包括

以下代码已从this Stanford video提供。强烈建议您观看前5分钟

一个可选是仅包含2个案例的枚举

enum Optional<T>{
    case None
    case Some(T)
}

let x: String? = nil //actually means:

let x = Optional<String>.None
let x :String? = "hello" //actually means:

let x = Optional<String>.Some("hello")
var y = x! // actually means:

switch x {
case .Some(let value): y = value
case .None: // Raise an exception
}

可选绑定:

let x:String? = something
if let y = x {
    // do something with y
}
//Actually means:

switch x{
case .Some(let y): print)(y) // or whatever else you like using 
case .None: break
}

当你说var john: Person?时,你实际上是这样说的:

enum Optional<Person>{
case .None
case .Some(Person)
}

以上枚举是否有任何名为apartment属性?你在任何地方看到它吗?那里有!但是,如果你打开它,即person!,那么你可以......它在幕后做的是:Optional<Person>.Some(Person(name: "John Appleseed"))

如果您定义了var john: Person而不是var john: Person?,那么您将不再需要使用!,因为Person本身确实有apartment的成员1}}

有关不推荐使用!进行展开的未来讨论,请参阅this Q&A