var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
另一个巧妙的技巧是,你可以使用逗号在打开它之后检查值的某个条件 .
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// This line pops up the error
destination.nameLabel.text = item.name
}
}
}
然后我发现我无法设置目标控制器出口的值,因为控制器尚未加载或初始化 .
所以我这样解决了:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// Created this method in the destination Controller to update its outlets after it's being initialized and loaded
destination.updateView(itemData: item)
}
}
}
目标控制器:
// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""
// Outlets
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
nameLabel.text = name
}
func updateView(itemDate: ObjectModel) {
name = itemDate.name
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c3 = BadContact()
c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct BadContact {
var address : Address!
}
struct Address {
var city : String
}
c1.address.city = c2.address!.city // ERROR: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Can you now tell me which object it is that was nil?
这次代码已经变得更加清晰了 . 你可以合理化并认为可能是强力打开的 address 参数 .
完整的代码是:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c2 = GoodContact()
c1.address.city = c2.address!.city
c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
if let city = c2.address?.city { // safest approach. But that's not what I'm talking about here.
c1.address.city = city
}
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct GoodContact {
var address : Address?
}
struct Address {
var city : String
}
8 回答
这个答案是社区维基 . 如果您觉得它可以做得更好,请随时编辑它!
背景:什么是可选的?
在Swift中,Optional是一个generic type,它可以包含一个值(任何类型),或者根本没有值 .
在许多其他编程语言中,特定的"sentinel"值通常用于表示缺少值 . 例如,在Objective-C中,
nil
(null pointer)表示缺少对象 . 但是当使用原始类型时,这会变得更加棘手 - 应该使用_904209来表示缺少整数,或者可能是INT_MIN
,还是其他整数?如果选择任何特定值来表示"no integer",则意味着它不能再被视为有效值 .Swift是一种类型安全的语言,这意味着该语言可以帮助您清楚代码可以使用的值的类型 . 如果您的部分代码需要String,则类型安全性会阻止您错误地将其传递给Int .
在Swift, any type can be made optional . 可选值可以采用原始类型的任何值,or特殊值
nil
.选项在类型上使用
?
后缀定义:nil
表示可选项中缺少值:(注意,这个nil与Objective-C中的nil不同 . 在Objective-C中,nil是缺少有效的对象指针;在Swift中,Optionals不限于对象/引用类型 . 可选行为类似于Haskell的也许 . )
为什么我得到“致命错误:在打开可选值时意外发现nil”?
为了访问可选的值(如果它有一个),你需要 unwrap 它 . 可以安全地或强制地打开可选值 . 如果强制解包一个可选项,并且它没有值,则程序将因上述消息而崩溃 .
Xcode将通过突出显示一行代码来向您显示崩溃 . 问题出在这条线上 .
这种崩溃可能发生在两种不同的强制解包中:
1.明确的力量展开
这是通过可选的
!
运算符完成的 . 例如:由于
anOptionalString
在这里是nil
,因此您将在强行打开它的行上发生崩溃 .2.隐式解包的选项
这些是在
!
之后定义的,而不是在类型之后的?
.假设这些选项包含一个值 . 因此,无论何时访问隐式展开的可选项,它都会自动为您强制解包 . 如果它不包含值,它将崩溃 .
为了找出导致崩溃的变量,您可以在单击以显示定义时按住⌥,您可以在其中找到可选类型 .
特别是IBOutlets通常是隐式解包的选项 . 这是因为您的xib或storyboard将在初始化后在运行时链接出口 . 因此,您应确保在加载插件之前不要访问插件 . 您还应检查storyboard / xib文件中的连接是否正确,否则在运行时值将为
nil
,因此当它们是隐式时会崩溃解开 .我什么时候应该强行打开一个可选的?
明确的力量展开
作为一般规则,您永远不应该明确强制使用
!
运算符解包可选项 . 可能存在使用!
是可接受的情况 - 但如果您100%确定可选项包含值,则应该只使用它 .虽然可能存在使用强制解包的情况,正如您所知道的可选项包含一个值 - 但是没有一个地方您无法安全地打开该可选项 .
隐式解包的选项
这些变量的设计使您可以将其分配推迟到代码的后面 . 在您访问它们之前,您有责任确保它们具有 Value . 然而,因为它们涉及力展开,它们仍然本质上是不安全的 - 因为它们假设你的值是非零的,即使分配nil是有效的 .
您应该只使用隐式解包的选项作为最后的手段 . 如果你可以使用lazy variable,或者为变量提供default value,你应该这样做,而不是使用隐式解包的可选项 .
但是,有一个few scenarios where implicitly unwrapped optionals are beneficial,您仍然可以使用各种方式安全地展开它们,如下所示 - 但您应该始终谨慎使用它们 .
我如何安全地处理Optionals?
检查可选项是否包含值的最简单方法是将其与
nil
进行比较 .但是,99.9%的时间使用选项时,你实际上想要访问它包含的值,如果它包含一个 . 为此,您可以使用可选绑定 .
可选绑定
可选Binding允许您检查可选项是否包含值 - 并允许您将展开的值分配给新变量或常量 . 它使用语法
if let x = anOptional {...}
或if var x = anOptional {...}
,具体取决于您是否需要在绑定后修改新变量的值 .例如:
这样做首先检查可选项是否包含值 . 如果是,则将“展开”值分配给新变量(
number
) - 然后您可以自由使用它,就像它是非可选的一样 . 如果optional不包含值,那么将调用else子句,如您所期望的那样 .什么是可选绑定,你可以同时打开多个选项 . 您可以使用逗号分隔语句 . 如果所有选项都被打开,该声明将成功 .
另一个巧妙的技巧是,你可以使用逗号在打开它之后检查值的某个条件 .
在if语句中使用可选绑定的唯一方法是,您只能从语句范围内访问unwrapped值 . 如果需要访问语句范围之外的值,可以使用guard语句 .
guard statement允许您定义成功条件 - 并且当前范围仅在满足该条件时才会继续执行 . 它们使用语法
guard condition else {...}
定义 .因此,要将它们与可选绑定一起使用,您可以执行以下操作:
(请注意,在保护体内,必须使用其中一个控制转移语句才能退出当前正在执行的代码的范围) .
如果
anOptionalInt
包含一个值,它将被解包并分配给新的number
常量 . 守卫之后的代码将继续执行 . 如果它不包含值 - 守护程序将执行括号内的代码,这将导致控制权的转移,以便紧接着的代码不会被执行 .关于guard语句的真正优点是现在可以在语句后面的代码中使用展开的值(因为我们知道将来的代码只有在可选的值有值时才能执行) . 这对于消除嵌套多个if语句创建的‘pyramids of doom’非常有用 .
例如:
Guards还支持if语句支持的相同巧妙的技巧,例如同时展开多个选项并使用
where
子句 .是否使用if或guard语句完全取决于将来的代码是否需要包含值的可选项 .
无合并运算符
Nil Coalescing Operator是ternary conditional operator的漂亮速记版本,主要用于将选项转换为非选项 . 它的语法为
a ?? b
,其中a
是可选类型,b
与a
的类型相同(尽管通常是非可选的) .它基本上让你说“如果
a
包含一个值,解开它 . 如果没有,则返回b
“ . 例如,您可以像这样使用它:这将定义
Int
类型的number
常量,如果它包含值,则包含anOptionalInt
的值,否则包含0
.这只是简写:
可选链接
您可以使用Optional Chaining来调用方法或访问可选项上的属性 . 这可以通过在使用变量名时使用
?
后缀来完成 .例如,假设我们有一个变量
foo
,类型为可选的Foo
实例 .如果我们想在
foo
上调用一个不返回任何内容的方法,我们可以简单地做:如果
foo
包含值,则将在其上调用此方法 . 如果没有,那么不会发生任何不好的事情 - 代码将继续执行 .(这与在Objective-C中将消息发送到nil的行为类似)
因此,这也可以用于设置属性以及调用方法 . 例如:
再说一次,如果
foo
是nil
,这里不会发生任何不好的事 . 您的代码将继续执行 .可选链接允许您执行的另一个巧妙的技巧是检查设置属性或调用方法是否成功 . 您可以通过将返回值与
nil
进行比较来完成此操作 .(这是因为一个可选的值将返回Void?而不是Void对一个不返回任何东西的方法)
例如:
但是,在尝试访问属性或调用返回值的方法时,事情会变得有点棘手 . 因为
foo
是可选的,所以从它返回的任何内容也是可选的 . 要解决此问题,您可以解包返回的选项使用上述方法之一 - 或在访问方法或调用返回值的方法之前解开foo
本身 .此外,顾名思义,您可以将这些陈述“链接”在一起 . 这意味着如果
foo
具有可选属性baz
,其具有属性qux
- 您可以编写以下内容:同样,因为
foo
和baz
是可选的,所以从qux
返回的值将始终是可选的,无论qux
本身是否是可选的 .map和flatMap
选项常常未被充分利用的功能是能够使用
map
和flatMap
功能 . 这些允许您将非可选变换应用于可选变量 . 如果可选项具有值,则可以对其应用给定的转换 . 如果它没有值,它将保持nil
.例如,假设您有一个可选字符串:
通过对它应用
map
函数 - 我们可以使用stringByAppendingString
函数将其连接到另一个字符串 .因为
stringByAppendingString
采用非可选字符串参数,所以我们无法直接输入可选字符串 . 但是,通过使用map
,如果anOptionalString
具有值,则可以使用allowstringByAppendingString
.例如:
但是,如果
anOptionalString
没有值,map
将返回nil
. 例如:flatMap
与map
类似,但它允许您从闭包体内返回另一个可选项 . 这意味着您可以将一个可选项输入到需要非可选输入的进程中,但可以输出一个可选项 .试试!
Swift的错误处理系统可以安全地与_904309一起使用:
如果
someThrowingFunc()
抛出错误,则会在catch
块中安全地捕获错误 .您在
catch
块中看到的error
常量尚未由我们声明 - 它由catch
自动生成 .您也可以自己声明
error
,它具有能够将其转换为有用格式的优点,例如:使用
try
这种方式是尝试,捕获和处理来自抛出函数的错误的正确方法 .还有
try?
吸收了错误:但是Swift的错误处理系统也提供了"force try"与
try!
的方法:这篇文章中解释的概念也适用于此:如果抛出错误,应用程序将崩溃 .
你应该只使用
try!
,如果你能证明它的结果永远不会在你的情境中失败 - 这是非常罕见的 .大多数情况下,您将使用完整的Do-Try-Catch系统 - 以及可选的
try?
,在极少数情况下,处理错误并不重要 .资源
Apple documentation on Swift Optionals
When to use and when not to use implicitly unwrapped optionals
Learn how to debug an iOS app crash
TL; DR回答
使用very few exceptions,此规则是黄金的:
避免使用!
声明变量可选(?),而不是隐式解包的选项(IUO)(!)
换句话说,而是使用:
var nameOfDaughter: String?
代替:
var nameOfDaughter: String!
使用if let或guard let解包可选变量
要么解开变量,请执行以下操作:
或者像这样:
这个答案旨在简洁,for full comprehension read accepted answer
这个问题出现在 ALL THE TIME 上 . 这是新Swift开发人员挣扎的第一件事 .
背景:
Swift使用“Optionals”的概念来处理可能包含值的值 . 在其他语言(如C)中,您可以在变量中存储值0,以指示它不包含任何值 . 但是,如果0是有效值怎么办?然后你可以使用-1 . 如果-1是有效值怎么办?等等 .
Swift选项允许您设置任何类型的变量以包含有效值或无值 .
当您声明变量意味着(键入x或没有值)时,在类型后面添加一个问号 .
可选实际上是一个容器,而不是包含给定类型的变量,或者什么也不包含 .
一个可选项需要“解包”才能获取内部值 .
“!” operator是一个“force unwrap”操作符 . 它说:“相信我 . 我知道我在做什么 . 我保证当这段代码运行时,变量不会包含nil . ”如果你错了,你会崩溃 .
除非你真的知道自己在做什么,否则请避免使用"!" force unwrap运算符 . 它可能是开始Swift程序员的最大崩溃源 .
如何处理期权:
还有很多其他方法可以处理更安全的选项 . 这里有一些(不是详尽的清单)
您可以使用“可选绑定”或“如果让”来说“如果此可选项包含值,则将该值保存到新的非可选变量中 . 如果可选项不包含值,则跳过此if语句的主体” .
以下是我们的
foo
可选绑定的可选绑定示例:请注意,在if语句的主体中仅存在使用可选替换时定义的变量(仅在“范围内”) .
或者,您可以使用警卫语句,如果变量为nil,则允许您退出函数:
在Swift 2中添加了Guard语句.Guard允许您通过代码保留“黄金路径”,并避免有时因使用“if let”可选绑定而导致的嵌套ifs级别不断增加 .
还有一个称为“nil coalescing operator”的结构 . 它采用“optional_var ?? replacement_val”的形式 . 它返回一个非可选变量,其类型与可选中包含的数据相同 . 如果optional包含nil,则返回“??”后面的表达式的值符号 .
所以你可以使用这样的代码:
您也可以使用try / catch或guard错误处理,但通常上面的其他技术之一更清晰 .
编辑:
另一个稍微更精细的选项是“隐式解包的选项 . 当我们宣布foo时,我们可以说:
在这种情况下,foo仍然是可选的,但你不必打开它来引用它 . 这意味着每当你尝试引用foo时,如果它为零则崩溃 .
所以这段代码:
即使我们没有强制解包foo,也会在引用foo的capitalizedString属性时崩溃 . 印刷品看起来不错,但事实并非如此 .
因此,您希望对隐式解包的选项非常小心 . (甚至可能完全避免它们,直到你对选项有充分的了解 . )
一句话:当你第一次学习Swift时,假装“!”字符不是语言的一部分 . 这可能会让你陷入困境 .
首先,您应该知道可选值是什么 . 你可以步骤The Swift Programming Launage
细节 .
其次,您应该知道可选值有两种状态 . 一个是完整值,另一个是零值 . 因此,在实现可选值之前,您应该检查它的状态 .
您可以使用
if let ...
或guard let ... else
等 .另一种方法是,如果你在实施之前没有声明,你也可以使用
var buildingName = buildingName ?? "buildingName"
.由于上述答案清楚地解释了如何安全地使用Optionals . 我会尝试解释什么是Optionals真的很快 .
声明可选变量的另一种方法是
var i : Optional<Int>
并且可选类型只是具有两种情况的枚举,即
所以要为我们的变量'i'指定一个nil . 我们可以做
var i = Optional<Int>.none
或者分配一个值,我们会传递一些值var i = Optional<Int>.some(28)
根据swift,'nil'是没有 Value 的 . 并创建一个用_904355初始化的实例我们必须遵循一个名为
ExpressibleByNilLiteral
的协议,如果你猜对了,那么只有Optionals
符合ExpressibleByNilLiteral
并且不鼓励遵守其他类型 .ExpressibleByNilLiteral
有一个名为init(nilLiteral:)
的方法,用nil初始化一个实例 . 您通常不会调用此方法,并且根据swift文档,不鼓励直接调用此初始化程序,因为编译器在使用nil
literal初始化Optional类型时会调用它 .甚至我自己也必须围绕Optionals:D Happy Swfting All包裹(没有双关语) .
当我尝试从准备segue方法设置我的Outlets值时,我遇到了这个错误,如下所示:
然后我发现我无法设置目标控制器出口的值,因为控制器尚未加载或初始化 .
所以我这样解决了:
目标控制器:
我希望这个答案可以帮助那些有同样问题的人,因为我发现明确的答案是理解选项及其工作方式的重要资源但却没有直接解决问题本身 .
基本上你试图在Swift只允许非零的地方使用nil值,通过“欺骗”编译器认为那里有nil值,从而允许你的app编译 .
有几种情况会导致这种致命错误:
如果
someVariable
为nil,那么你要保证编译器你永远不会有nil值 . 并猜猜如果发生这种情况会发生什么?解?使用可选绑定(也就是if-let),在那里进行变量处理:
在这里通过强制转换你告诉编译器不再担心,因为你总是有一个
Rectangle
实例 . 只要这样,你就不用担心了 . 当您或您的同事从项目开始循环非矩形值时,问题就开始了 .解?使用可选绑定(也就是if-let),在那里进行变量处理:
现在,如果没有人通过将
name
属性设置为nil
来解决它,那么它会按预期工作,但是如果User
是从缺少name
键的JSON初始化的,那么在尝试使用该属性时会出现致命错误 .解?不要使用它们:)除非你确定102%的 property 总是非零 Value 到需要使用的时间 . 但是,在大多数情况下,将其转换为可选或非可选将起作用 . 使其成为非可选项也将导致编译器通过告知您错过的代码路径为该属性提供值来帮助您
现在,如果您错过了连接XIB编辑器的插座,那么只要您想要使用插座,应用程序就会崩溃 . 解?确保所有插座都已连接 . 或者在它们上使用
?
运算符:emailTextField?.text = "my@email.com"
. 或者将插座声明为可选,但在这种情况下,编译器会强制您将其解包到代码中 .现在,如果没有指定可空性注释(显式或通过
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
),那么name
属性将作为String!
(一个IUO - 隐式解包的可选项)在Swift中导入 . 一旦一些swift代码想要使用该值,它将崩溃,在零的情况下 .解?在您的Objective-C代码中添加可空性注释 . 但要注意,Objective-C编译器在可空性方面有点宽容,即使你明确地将它们标记为
nonnull
,也可能最终得到nil值 .这更像是一个重要的注释,为什么在调试
nil
值时,为什么隐式解包的选项可能具有欺骗性 .考虑以下代码:它编译时没有错误/警告:
然而在运行时它会出现以下错误:致命错误:在解开Optional值时意外发现nil
Can you tell me which object is nil?
你不能!
完整的代码是:
长话短说使用
var address : Address!
你是 hiding 变量可能是其他读者的nil
的可能性 . 当它崩溃你就像“到底是什么?!我的address
不是可选的,为什么我会崩溃?!因此,最好这样写:
Can you now tell me which object it is that was nil?
这次代码已经变得更加清晰了 . 你可以合理化并认为可能是强力打开的
address
参数 .完整的代码是: