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”,正如我最初所想的那样。我只是误解了 Person
和 Person?
是完全不同的类型。一旦我掌握了这一点,所有其他 ?
、!
的疯狂以及下面的精彩答案就变得更有意义了。
“打开实例”是什么意思?为什么有必要?
据我所知(这对我来说也很新鲜)......
术语“包装”意味着我们应该将 Optional 变量视为礼物,包裹在闪亮的纸中,可能(可悲!)是空的。
当“包装”时,可选变量的值是一个具有两个可能值的枚举(有点像布尔值)。此枚举描述变量是否包含值 (Some(T)
),或不包含值 (None
)。
如果有值,这可以通过“解包”变量来获得(从 Some(T)
获取 T
)。
john!.apartment = number73 与 john.apartment = number73 有何不同? (转述)
如果您编写一个可选变量的名称(例如,文本 john
,没有 !
),这指的是“包装的”枚举 (Some/None),而不是值本身 (T)。所以 john
不是 Person
的实例,并且它没有 apartment
成员:
john.apartment
// 'Person?' does not have a member named 'apartment'
实际的 Person
值可以通过多种方式展开:
“强制拆包”:约翰! (如果存在,则给出 Person 值,如果为 nil,则给出运行时错误)
"可选绑定": if let p = john { println(p) } (如果值存在则执行 println)
“可选链接”: john?.learnAboutSwift() (如果值存在则执行这个虚构的方法)
我猜你会选择其中一种方式来展开,这取决于在 nil 情况下应该发生什么,以及这种情况的可能性有多大。这种语言设计强制显式处理 nil 情况,我认为这提高了 Obj-C 的安全性(很容易忘记处理 nil 情况)。
更新:
感叹号也用于声明“隐式解包选项”的语法中。
在到目前为止的示例中,john
变量已声明为 var john:Person?
,它是一个 Optional。如果您想要该变量的实际值,则必须使用上述三种方法之一解开它。
如果将其声明为 var john:Person!
,则该变量将是隐式展开的 Optional(请参阅 Apple 书中带有此标题的部分)。访问值时无需解包此类变量,无需额外语法即可使用 john
。但苹果的书说:
当变量有可能在以后变为 nil 时,不应使用隐式展开的选项。如果您需要在变量的生命周期内检查 nil 值,请始终使用正常的可选类型。
更新 2:
Mike Ash 的文章“Interesting Swift Features”为可选类型提供了一些动机。我觉得写的很好,很清晰。
更新 3:
关于感叹号隐式展开的可选使用的另一篇有用的文章:Chris Adamson 的“Swift and the Last Mile”。文章解释说,这是 Apple 的一种实用措施,用于声明其 Objective-C 框架使用的可能包含 nil 的类型。将类型声明为可选(使用 ?
)或隐式展开(使用 !
)是“安全性和便利性之间的权衡”。在本文给出的示例中,Apple 选择将类型声明为隐式展开,从而使调用代码更方便,但安全性较低。
也许 Apple 可能会在未来梳理他们的框架,消除隐式展开(“可能永远不会为 nil”)参数的不确定性,并用可选的(“当然可能是 nil,特别是 [希望,记录在案!] 情况”)或标准非- 可选(“从不为零”)声明,基于其 Objective-C 代码的确切行为。
这是我认为的区别:
var john: Person?
意味着 john 可以为 nil
john?.apartment = number73
编译器会将这一行解释为:
if john != nil {
john.apartment = number73
}
尽管
john!.apartment = number73
编译器会将这一行简单解释为:
john.apartment = number73
因此,使用 !
将解开 if 语句,并使其运行得更快,但如果 john 为 nil,则会发生运行时错误。
所以这里的 wrap 并不意味着它是内存包裹的,而是它意味着它是代码包裹的,在这种情况下它是用一个 if 语句包裹的,而且因为 Apple 非常关注运行时的性能,他们想给你一个方法来使您的应用程序以最佳性能运行。
更新:
年后回到这个答案,因为我在 Stackoverflow 中获得了最高的声誉 :) 我当时有点误解了展开的含义。现在经过 4 年,我相信在这里展开的意义是将代码从其最初的紧凑形式扩展。这也意味着消除该对象周围的模糊性,因为我们不确定它是否为 nil。就像 answer of Ashley above 一样,把它想象成一个不能包含任何东西的礼物。但我仍然认为解包是代码解包,而不是使用枚举的基于内存的解包。
TL;博士
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."
let f: String! = "hello"
,然后执行 print(f)
,则输出为 Optional("hello")
而不仅仅是 "hello"
。
如果 john 是一个可选的 var(如此声明)
var john: Person?
那么 john 可能没有任何价值(用 ObjC 的说法,nil 值)
感叹号基本上告诉编译器“我知道这有一个值,你不需要测试它”。如果你不想使用它,你可以有条件地测试它:
if let otherPerson = john {
otherPerson.apartment = number73
}
this 的内部只会评估 john 是否具有值。
“let possibleString: String? = "An optional string." println(possibleString!) // requires an exclamation mark to access its value // prints "An optional string.”
但它可以在没有 ! 的情况下正常工作。这里似乎有些奇怪。
John?
和 John
是两种不同的类型。一个是 Optional Person 类型,另一个是 Person 类型。可选的 Person 需要先解包,然后才能将 Person 取出。但正如你所说,这似乎至少在这些情况下发生,而无需实际做任何事情。这似乎使!多余的。除非!始终是可选的,但建议采取措施来捕获编译时错误。有点像将 vars/let 分配给特定类型可以是显式的 let John: Person = ...
,但也可以推断为 let John = ...
。
一些大图视角可以添加到其他有用但更以细节为中心的答案中:
在 Swift 中,感叹号出现在几种情况下:
强制展开:让 name = nameLabel!.text
隐式展开的选项: var logo: UIImageView!
强制转换:logo.image = thing as! UIImage
未处理的异常:试试! NSJSONSerialization.JSONObjectWithData(数据, [])
每一个都是不同的语言结构,具有不同的含义,但它们都有三个重要的共同点:
1. 感叹号绕过了 Swift 的编译时安全检查。
当您在 Swift 中使用 !
时,您实际上是在说:“嘿,编译器,我知道您认为这里可能会发生错误,但我完全确定知道它永远不会。”
并非所有有效代码都适合 Swift 的编译时类型系统——或者任何语言的静态类型检查,就此而言。在某些情况下,您可以从逻辑上证明永远不会发生错误,但您无法向编译器证明它。这就是为什么 Swift 的设计师首先添加了这些功能。
但是,无论何时使用 !
,您都排除了错误的恢复路径,这意味着……
2.感叹号是潜在的崩溃。
感叹号还说:“嘿,斯威夫特,我非常确定这个错误永远不会发生,你让我的整个应用程序崩溃比我为它编写恢复路径更好。”
这是一个危险的断言。它可能是正确的:在您仔细考虑过代码的不变量的关键任务代码中,虚假输出可能比崩溃更糟糕。
但是,当我在野外看到 !
时,很少有人如此认真地使用它。相反,它通常意味着,“这个值是可选的,我并没有认真考虑为什么它可能是 nil 或者如何正确处理这种情况,但是添加 !
使它编译……所以我的代码是正确的,对吧?”
当心感叹号的傲慢。反而…
3.感叹号最好少用。
这些 !
构造中的每一个都有一个 ?
对应项,它强制您处理 error/nil 情况:
条件展开: if let name = nameLabel?.text { ... }
可选: var logo: UIImageView?
条件转换:logo.image = thing as? UIImage
Nil-on-failure 异常:试试? NSJSONSerialization.JSONObjectWithData(数据, [])
如果您想使用 !
,最好仔细考虑为什么不使用 ?
。如果 !
操作失败,让您的程序崩溃真的是最好的选择吗? 为什么该值是可选的/可失败的?
在 nil/error 情况下,您的代码是否有合理的恢复路径?如果是这样,请编码。
如果它不可能为零,如果错误永远不会发生,那么是否有一种合理的方法来重新设计你的逻辑,以便编译器知道这一点?如果是这样,那就去做;您的代码将不易出错。
有时没有合理的方法来处理错误,而简单地忽略错误——从而处理错误的数据——会比崩溃更糟糕。那些是使用强制展开的时候。
我会定期在整个代码库中搜索 !
并审核它的每次使用。很少有用法经得起审查。 (在撰写本文时,整个 Siesta 框架正好有 two instances 个。)
这并不是说您应该从不在您的代码中使用 !
— 只是您应该谨慎地使用它,并且永远不要将其设为默认选项。
func isSubscriptionActive(receiptData: NSDictionary?) -> Bool { if(receiptData == nil) { return false; } return (hasValidTrial(receiptData!) || isNotExpired(receiptData!)) && isNotCancelled(receiptData!) }
给定 3. 有没有更好的写法?
func isSubscriptionActive(receiptData: NSDictionary?) -> Bool { guard let nonNilReceiptData = receiptData else { return false} return (hasValidTrial(nonNilReceiptData) || isNotExpired(nonNilReceiptData)) && isNotCancelled(nonNilReceiptData) }
john
是可选的 var
,它可以包含 nil
值。为确保该值不为零,请在 var
名称的末尾使用 !
。
从文档
“一旦您确定可选项确实包含一个值,您就可以通过在可选项名称的末尾添加一个感叹号 (!) 来访问其基础值。感叹号有效地表示,“我知道这个可选的肯定是有价值的;请使用它。”
检查非零值的另一种方法是(可选展开)
if let j = json {
// do something with j
}
john.apartment = number73
还说“我知道这个可选的肯定有一个价值;请使用它。”......
这里有些例子:
var name:String = "Hello World"
var word:String?
其中 word
是可选值。表示它可能包含也可能不包含值。
word = name
这里 name
有一个值,所以我们可以分配它
var cow:String = nil
var dog:String!
其中 dog
被强制解包意味着它必须包含一个值
dog = cow
应用程序将崩溃,因为我们将 nil
分配给 unwrapped
var c:Int = nil
将得到:“Nil 无法初始化指定类型 'int'”
在这种情况下...
var John:人!
这意味着,最初 John 将具有 nil 值,它将被设置并且一旦设置将永远不会再被 nil 引导。因此,为了方便起见,我可以使用更简单的语法来访问可选 var,因为这是一个“隐式展开的可选”
如果你来自 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 来代替,除非您非常确定该值不会为 nil。这种诡计意味着可以对语言进行强类型化,而无需强制您测试值是否到处都是 nil。
在实践中,它并没有真正表现得像那样,因为工作是由编译器完成的。在高层有一个与 Foo
分开的类型 Foo?
,它阻止接受类型 Foo
的函数接收 nil 值,但在低层,可选值不是真正的对象,因为它没有属性或方法;实际上,它很可能是一个指针,在强制展开时可以通过适当的测试为 NULL(0)。
在其他情况下,您会在类型上看到感叹号,例如:
func foo(bar: String!) {
print(bar)
}
这大致相当于接受带有强制展开的可选,即:
func foo(bar: String?) {
print(bar!)
}
您可以使用它来拥有一个在技术上接受可选值但如果它为 nil 则会出现运行时错误的方法。在当前版本的 Swift 中,这显然绕过了 is-not-nil 断言,因此您将遇到低级错误。通常不是一个好主意,但在从另一种语言转换代码时它会很有用。
这 !意味着您正在强制解开对象!跟随。更多信息可在 Apple 文档中找到,可在此处找到:https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/TheBasics.html
如果您熟悉 C#,这就像 Nullable 类型,也使用问号声明:
Person? thisPerson;
并且这种情况下的感叹号等效于访问可空类型的 .Value 属性,如下所示:
thisPerson.Value
在没有值的目标 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
{..}
这 !在对象的末尾表示该对象是可选的,如果它可以返回 nil,则展开。这通常用于捕获可能导致程序崩溃的错误。
简而言之(!):在您声明一个变量并且您确定该变量持有一个值之后。
let assumedString: String! = "Some message..."
let implicitString: String = assumedString
否则,您必须在每次传递值后都执行此操作...
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark
对于 Google 员工:
john!.department
...告诉编译器:
我知道约翰是可选的
像有价值一样使用它
如果没有就崩溃
在生产中,使用 guard let
或 if let
来处理无值和无效硬崩溃的情况。
John 是一个可选的 Person,这意味着它可以持有一个值或为零。
john.apartment = number73
如果 john 不是可选的,则使用。由于 john 永远不会是 nil,我们可以确定它不会以 nil 值调用公寓。尽管
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.apartment = number73
,那么除非我输入“!”,否则不会导致错误在约翰之后?
john
可选的事实不是告诉编译器我需要它是非零的吗?...在您的 var jack: Person = john!
示例中,没有缺少一个?在 Person 告诉编译器您需要 john
为非零之后?总的来说,!
似乎有点多余......我仍然觉得我在 !
的“为什么”部分遗漏了一些东西......
简单地说,感叹号意味着一个可选的被解包。可选项是可以有值或没有值的变量——因此您可以使用 if let 语句 as shown here 检查变量是否为空,然后强制解包。如果你强制解开一个空的可选选项,你的程序会崩溃,所以要小心!可选项是通过在对变量的显式赋值末尾放置一个问号来声明的,例如我可以这样写:
var optionalExample: String?
这个变量没有价值。如果我打开它,程序会崩溃,Xcode 会告诉你你试图打开一个值为 nil 的可选项。
希望有帮助。
简单来说
USING 感叹号表示变量必须包含非零值(它永远不会是零)
整个故事从 swift 的一个特性开始,称为可选变量。这些是可能有值或可能没有值的变量。一般来说,swift不允许我们使用未初始化的变量,因为这可能会导致崩溃或意外原因,并且还会为后门提供占位符。因此,为了声明一个初始值未确定的变量,我们使用“?”。当声明这样的变量时,要将其用作某些表达式的一部分,必须在使用前对其进行解包,解包是一种操作,通过该操作可以发现变量的值,这适用于对象。如果您尝试使用它们而不打开包装,您将遇到编译时错误。要解开作为可选 var 的变量,请使用感叹号“!”用来。
现在有时您知道这些可选变量将由系统(例如)或您自己的程序分配值,但稍后,例如 UI 出口,在这种情况下,而不是使用问号“?”声明可选变量。我们用 ”!”。
因此系统知道这个用“!”声明的变量现在是可选的,没有价值,但会在其生命周期的后期收到一个价值。
因此,感叹号有两种不同的用法,1. 声明一个可选变量,稍后肯定会收到值 2. 在表达式中使用可选变量之前解包。
我希望以上描述避免了过多的技术内容。
如果您将它用作可选项,它会解开可选项并查看是否存在某些内容。如果在 if-else 语句中使用它,则为 NOT 的代码。例如,
if (myNumber != 3){
// if myNumber is NOT 3 do whatever is inside these brackets.
)
可选变量可能包含一个值,也可能不包含
案例 1:var myVar:String? = "Something"
案例 2:var myVar:String? = nil
现在如果你问 myVar!,你是在告诉编译器返回一个值,以防万一它返回 "Something"
在情况 2 中它会崩溃。
意义 !标记将强制编译器返回一个值,即使它不存在。这就是名称强制展开的原因。
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
问你自己
类型人吗?有公寓成员/财产吗?或者
类型人是否有公寓成员/财产?
如果您无法回答这个问题,请继续阅读:
要了解您可能需要对泛型有超基本的了解。请参阅here。 Swift 中的很多东西都是使用泛型编写的。包括选项
this Stanford video 提供了以下代码。强烈推荐你看前5分钟
Optional 是一个只有 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
的 property?你在任何地方看到它吗?它根本就不在那里!但是,如果您打开它,即执行 person!
,那么您可以......它在引擎盖下的作用是:Optional<Person>.Some(Person(name: "John Appleseed"))
如果您定义 var john: Person
而不是:var john: Person?
,那么您将不再需要使用 !
,因为 Person
本身确实有 apartment
的成员
关于为什么有时不建议使用 !
展开的未来讨论,请参阅 this Q&A
不定期副业成功案例分享