什么是“致命错误:意外地发现零,而解包可选值”是什么意思?
我的Swift程序崩溃与EXC_BAD_INSTRUCTION
和这个错误。 这是什么意思,我该如何解决?
致命错误:意外地发现零,而解包一个可选的值
这篇文章的目的是为了收集“意外发现无”问题的答案,所以它们不是零散的,很难find。 随意添加自己的答案或编辑现有的维基答案。
这个答案是社区维基 。 如果你觉得它可以做的更好,随时编辑 !
背景:什么是可选的?
在Swift中, Optional
是一个genericstypes ,可以包含任何types的值,或者根本没有值。
在许多其他编程语言中,通常使用特定的“标记”值来指示缺less值 。 例如,在Objective-C中, nil
( 空指针 )表示缺less一个对象。 但是,在使用原始types时,这会变得更加棘手 – 应该用-1
来指示缺less整数,或者可能是INT_MIN
或其他整数? 如果任何特定的值被select为“没有整数”,这意味着它不能被视为有效的值。
Swift是一种types安全的语言,这意味着这种语言可以帮助您清楚您的代码可以使用的值的types。 如果你的代码的一部分需要一个string,types安全防止你错误地传递一个Int。
在Swift中, 任何types都是可选的 。 可选值可以取原始types的任何值,也可以取特殊值nil
。
可选项用一个?
后缀types:
var anInt: Int = 42 var anOptionalInt: Int? = 42 var anotherOptionalInt: Int? // `nil` is the default when no value is provided
在可选项中缺less值表示为nil
:
anOptionalInt = nil
(注意,这个nil
和Objective-C中的nil
是不一样的,在Objective-C中, nil
是没有有效的对象指针 ;在Swift中,Optionals不局限于对象/引用types,可选的行为与Haskell 也许 。)
为什么我得到“ 致命的错误:意外地发现零,而解包可选值 ”?
为了访问一个可选的值(如果它有一个),你需要打开它。 可选值可以安全或强制解包。 如果你强制解开一个可选项,并且没有值,你的程序将会崩溃,并显示上面的消息。
Xcode会通过突出显示一行代码来向您显示崩溃。 这一行发生问题。
这种崩溃可能发生在两种不同types的force-unwrap上:
1.显式强制解包
这是用!
运营商在一个可选的。 例如:
let anOptionalString: String? print(anOptionalString!) // <- CRASH
由于anOptionalString
在这里是nil
,所以你会在你强制解包它的行上发生崩溃。
2.隐式解包选项
这些都是用一个!
,而不是一个?
之后的types。
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
这些可选项被假定为包含一个值。 因此,无论何时访问隐式解包的可选项,它都会自动为您解压缩。 如果它不包含值,则会崩溃。
print(optionalDouble) // <- CRASH
为了确定哪个variables导致崩溃,可以在按住⌥键的同时显示定义,在这里可以find可选的types。
IBOutlets尤其通常是隐含的解包select权。 这是因为在初始化之后 ,xib或storyboard会在运行时连接sockets。 因此,您应该确保在加载之前不要访问sockets。还应该检查storyboard / xib文件中的连接是否正确,否则在运行时值将nil
,因此在隐式解开。
我什么时候应该强制解包一个可选的?
显式强制解包
作为一般规则,你永远不应该明确强制解包一个可选的!
运营商。 有可能会使用!
是可以接受的 – 但只有在100%确定可选项包含值的情况下,才应该使用它。
虽然可能有场合可以使用强制解包,但是正如你所知道的,一个可选的值包含了一个值 – 没有一个地方你不能安全地解开这个可选的东西。
隐含解包选项
这些variables的devise是为了让你可以延迟他们的任务,直到以后在你的代码中。 在您访问它之前,您有责任确保它们有价值。 然而,因为它们涉及力量的解开,所以它们本质上仍然是不安全的 – 因为它们假定你的价值不是零,即使分配零是有效的。
作为最后的手段,你应该只使用隐含的解包选项。 如果你可以使用一个懒惰的variables ,或者为一个variables提供一个默认值 – 你应该这样做,而不是使用隐式解包的可选项。
然而, 有些情况下隐含的解包选项是有益的 ,而且您仍然可以使用各种方式安全地解开它们(如下所示) – 但是您应该始终谨慎地使用它们。
我怎样才能安全地处理选项?
检查一个可选值是否包含一个值的最简单方法是将其与nil
进行比较。
if anOptionalInt != nil { print("Contains a value!") } else { print("Doesn't contain a value.") }
然而,使用optionals的时候,99.9%的时间,你实际上想要访问它包含的值,如果它包含一个。 要做到这一点,你可以使用可选的绑定 。
可选的绑定
可选的绑定允许你检查一个可选值是否包含一个值,并允许你将展开的值赋给一个新的variables或常量。 它使用语法, if let x = anOptional {...}
或者if var x = anOptional {...}
,这取决于您是否需要修改绑定它后的新variables的值。
例如:
if let number = anOptionalInt { print("Contains a value! It is \(number)!") } else { print("Doesn't contain a number") }
这是做的首先检查可选包含一个值。 如果是这样 ,那么“unwrapped”的值被分配给一个新的variables( number
) – 然后你可以自由使用,就好像它是非可选的一样。 如果可选项不包含值,那么将会调用else子句,就像您期望的那样。
什么是可选绑定,你可以同时拆开多个选项。 你可以用逗号分隔这些语句。 如果所有的可选项都被解开了,声明将会成功。
var anOptionalInt : Int? var anOptionalString : String? if let number = anOptionalInt, 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") }
另一个巧妙的诀窍是你可以使用where
子句在解包之后检查值的某个条件。
if let number = anOptionalInt where number > 0 { print("anOptionalInt contains a value: \(number), and it's greater than zero!") }
在if语句中使用可选绑定的唯一方法就是只能从语句的范围内访问解包的值。 如果您需要访问声明范围之外的值,则可以使用警戒声明 。
警戒语句允许您定义成功的条件 – 当前的作用域只有在满足条件的情况下才会继续执行。 它们使用语法guard condition else {...}
来定义。
所以,要使用可选的绑定,你可以这样做:
guard let number = anOptionalInt else { return }
(请注意,在警卫体内,您必须使用其中一个控制转移语句才能退出当前正在执行的代码的范围)。
如果anOptionalInt
包含一个值,它将被解包并分配给新的number
常量。 守卫之后的代码将继续执行。 如果它不包含一个值 – 守卫将执行括号内的代码,这将导致控制权的转移,以便紧接着的代码不会被执行。
关于guard语句的真正整洁的事情是解开的值现在可以在语句后面的代码中使用(因为我们知道,如果可选值有值,将来的代码只能执行)。 这对于消除嵌套多个if语句创build的“厄运金字塔”非常有用 。
例如:
guard let number = anOptionalInt else { return } print("anOptionalInt contains a value, and it's: /(number)!")
卫兵也支持if语句支持的相同巧妙的技巧,例如同时解包多个选项,并使用where
子句。
是否使用if或guard声明完全取决于将来的代码是否需要可选项来包含值。
无合并操作员
无合并运算符是三元条件运算 符的一种漂亮的简写forms,主要用于将可选项转换为非可选项。 它的语法a ?? b
a ?? b
,其中a
是一个可选types, b
是与b
相同的types(尽pipe通常是非可选的)。
它基本上让你说“如果a
包含一个值,解开它。 如果不是,则返回b
“。 例如,你可以像这样使用它:
let number = anOptionalInt ?? 0
这将定义一个Int
types的number
常数,它将包含anOptionalInt
的值(如果它包含一个值),否则包含0
。
这只是简写:
let number = anOptionalInt != nil ? anOptionalInt! : 0
可选的链接
您可以使用可选链接来调用方法或访问可选的属性。 这只是通过后缀variables名称?
当使用它。
例如,假设我们有一个variablesfoo
,其types是一个可选的Foo
实例。
var foo : Foo?
如果我们想调用一个不返回任何东西的方法,我们可以简单地做:
foo?.doSomethingInteresting()
如果foo
包含一个值,这个方法将被调用。 如果没有,就会发生什么坏事 – 代码将继续执行。
(这与在Objective-C中向消息发送消息的行为类似)
因此,这也可以用来设置属性以及调用方法。 例如:
foo?.bar = Bar()
同样,如果foo
是nil
这里也不会发生什么坏事。 您的代码将继续执行。
另一个可选链接让你可以做的一个巧妙的技巧是检查设置属性或调用方法是否成功。 你可以通过比较返回值到nil
来做到这一点。
(这是因为一个可选的值将返回Void?
而不是返回任何东西的方法上的Void
)
例如:
if (foo?.bar = Bar()) != nil { print("bar was set successfully") } else { print("bar wasn't set successfully") }
然而,当试图访问属性或调用返回值的方法时,事情会变得更加棘手。 因为foo
是可选的,所以从它返回的东西也是可选的。 为了解决这个问题,你可以使用上面的方法之一打开返回的选项,或者在访问方法或调用返回值的方法之前解开foo
本身。
而且,顾名思义,您可以将这些陈述“连锁”在一起。 这意味着如果foo
有一个可选属性baz
,它有一个属性qux
– 你可以这样写:
let optionalQux = foo?.baz?.qux
同样,因为foo
和baz
是可选的, qux
返回的值将永远是可选的,无论qux
本身是否可选。
map
和flatMap
使用optionals的一个经常被忽略的function是能够使用map
和flatMap
函数。 这些允许您将非可选转换应用于可选variables。 如果一个可选项有一个值,你可以对其应用一个给定的转换。 如果它没有价值,它将保持nil
。
例如,假设您有一个可选的string:
let anOptionalString:String?
通过应用map
函数 – 我们可以使用stringByAppendingString
函数将其连接到另一个string。
因为stringByAppendingString
接受一个非可选的string参数,所以我们不能直接input我们的可选string。 但是,通过使用map
,如果anOptionalString
具有值,则可以使用allow stringByAppendingString
。
例如:
var anOptionalString:String? = "bar" anOptionalString = anOptionalString.map {unwrappedString in return "foo".stringByAppendingString(unwrappedString) } print(anOptionalString) // Optional("foobar")
但是,如果anOptionalString
没有值, map
将返回nil
。 例如:
var anOptionalString:String? anOptionalString = anOptionalString.map {unwrappedString in return "foo".stringByAppendingString(unwrappedString) } print(anOptionalString) // nil
flatMap
工作方式与map
相似,只不过它允许你从闭包体中返回另一个可选项。 这意味着您可以将一个可选项input到需要非可选input的stream程中,但可以输出一个可选项。
try!
Swift的error handling系统可以安全地与Do-Try-Catch一起使用 :
do { let result = try someThrowingFunc() } catch { print(error) }
如果someThrowingFunc()
抛出一个错误,错误将被安全地捕获到catch
块中。
在catch
块中看到的error
常量没有被我们声明 – 它是由catch
自动生成的。
您也可以自己声明error
,它具有能够将其转换为有用格式的优势,例如:
do { let result = try someThrowingFunc() } catch let error as NSError { print(error.debugDescription) }
try
这种方式是尝试,捕捉和处理来自抛出函数的错误的正确方法。
还有try?
这吸收了错误:
if let result = try? someThrowingFunc() { // cool } else { // handle the failure, but there's no error information available }
但是Swift的error handling系统也提供了一种“尝试”的方法try!
:
let result = try! someThrowingFunc()
在这篇文章中解释的概念也适用于这里:如果错误被抛出,应用程序将崩溃。
你应该只使用try!
如果你能certificate其结果永远不会在你的背景下失败 – 这是非常罕见的。
大多数情况下,您将使用完整的Do-Try-Catch系统 – 可选的系统,请try?
在极less数情况下,处理错误并不重要。
资源
- Apple Swift Optionals上的文档
- 何时使用以及何时不使用隐式解包选项
- 了解如何debuggingiOS应用程序崩溃
TL; DR答案
除了极less数例外 ,这条规则是黄金的:
避免使用!
声明variables可选( ?
),而不是隐式解包可选项(IUO)( !
)
换句话说,宁可使用:
var nameOfDaughter: String?
代替:
var nameOfDaughter: String!
使用if let
或guard let
可选variables
要么像这样解包variables:
if let nameOfDaughter = nameOfDaughter { print("My daughters name is: \(nameOfDaughter)" }
或者像这样:
guard let nameOfDaughter = nameOfDaughter else { return } print("My daughters name is: \(nameOfDaughter)"
这个答案的目的是简洁, 全面理解阅读接受的答案
这个问题出现在所有这一切的时间 。 这是Swift新开发人员努力的第一件事情之一。
背景:
Swift使用“Optionals”的概念来处理可能包含值的值。 在像C这样的其他语言中,您可能会将值存储在variables中以指示它不包含任何值。 但是,如果0是一个有效值呢? 那么你可以使用-1。 如果-1是一个有效的值呢? 等等。
Swift选项允许你设置任何types的variables来包含一个有效的值或者没有值。
当你声明一个variables意味着(键入x或没有值)时,你在types后面加一个问号。
一个可选的实际上是一个容器,而不是包含一个给定types的variables,或者什么也不是。
一个可选的需要被“解包”,以获取内部的价值。
“!” 运营商是一个“解包”运营商。 它说:“相信我,我知道我在做什么,我保证当这个代码运行时,variables不会包含零。 如果你错了,你会崩溃。
除非你真的知道你在做什么,否则避免“!” 强拆运营商。 它可能是开始Swift程序员崩溃的最大来源。
如何处理可选项:
处理更安全的期权还有很多其他方法。 这里有一些(不完整的列表)
你可以使用“可选绑定”或“如果让”来说“如果这个可选值包含一个值,将该值保存到一个新的非可选variables中。如果可选值不包含值,则跳过这个if语句的正文”。
这是一个可选的绑定与我们的foo
可选的例子:
if let newFoo = foo //If let is called optional binding. { print("foo is not nil") } else { print("foo is nil") }
请注意,您在使用可选投标时定义的variables仅在if语句正文中存在(仅在“范围内”)。
或者,您可以使用guard语句,如果variables为nil,则可以让您退出函数:
func aFunc(foo: Int?) { guard let newFoo = input else { return } //For the rest of the function newFoo is a non-optional var }
Guard语句是在Swift 2中添加的。Guard允许您通过代码保留“黄金path”,并避免使用“if let”可选绑定有时导致的不断增加的嵌套ifs级别。
还有一个叫做“零合并运算符”的构造。 它采用“optional_var ?? replacement_val”的forms。 它返回一个非可选variables,其types与可选的数据相同。 如果可选包含零,则返回expression式的值“??” 符号。
所以你可以使用这样的代码:
let newFoo = foo ?? "nil" // "??" is the nil coalescing operator print("foo = \(newFoo)")
您也可以使用try / catch或guarderror handling,但通常上述其他技术之一更清晰。
编辑:
另外一个稍微更微妙的select权问题就是“隐含的解包的select权。当我们声明富时,我们可以说:
var foo: String!
在这种情况下,foo仍然是可选的,但是您不必拆开它来引用它。 这意味着任何时候你尝试引用foo,如果它是零,就会崩溃。
所以这个代码:
var foo: String! let upperFoo = foo.capitalizedString
即使我们不强制展开foo,也会引用foo的大写字母属性。 印刷看起来不错,但不是。
因此,你想要非常小心隐式unwrapped选项。 (甚至在完全理解可选项之前,甚至可以完全避免)。
底线:当你第一次学习Swift时,假装“!” 字符不是语言的一部分。 这很可能会让你陷入困境。
首先,你应该知道什么是可选值。 你可以进入Swift编程Launage
为细节。
其次,你应该知道可选值有两个状态。 一个是完整的价值,另一个是零价值。 所以在你实现一个可选的值之前,你应该检查它是哪个状态。
你可以使用, if let ...
或者guard let ... else
等等。
另一种方式,如果你不想在你的工具之前检查它的状态,你也可以使用var buildingName = buildingName ?? "buildingName"
改为var buildingName = buildingName ?? "buildingName"
。