我的 Swift 程序因 EXC_BAD_INSTRUCTION
和以下类似错误之一而崩溃。这个错误是什么意思,我该如何解决?
Fatal error: Unexpectedly found nil while unwrapping an Optional value
要么
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
这篇文章旨在收集“意外发现为零”问题的答案,以免它们分散而难以找到。随意添加您自己的答案或 edit 现有的维基答案。
最佳答案
这个答案是 community wiki 。如果您觉得它可以做得更好,请随时 edit it !
背景:什么是可选?
在 Swift 中, Optional<Wrapped>
是 option type :它可以包含来自原始(“Wrapped”)类型的任何值,或者根本没有值(特殊值 nil
)。一个可选值必须是 展开 才能使用。
可选的是 generic type ,这意味着 Optional<Int>
和 Optional<String>
是不同的类型 - <>
内部的类型称为 Wrapped 类型。在引擎盖下,Optional 是一个 enum,有两种情况: .some(Wrapped)
和 .none
,其中 .none
相当于 181343145 。
可以使用命名类型 nil
或(最常见)作为带有 Optional<T>
后缀的简写来声明可选项。
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
var aVerboseOptionalInt: Optional<Int> // equivalent to `Int?`
anOptionalInt = nil // now this variable contains nil instead of an integer
Optionals 是一个简单而强大的工具,可以在编写代码时表达你的假设。编译器可以使用此信息来防止您犯错误。从 The Swift Programming Language :Swift is a type-safe language, which means the language helps you to be clear about the types of values your code can work with. If part of your code requires a
String
, type safety prevents you from passing it anInt
by mistake. Likewise, type safety prevents you from accidentally passing an optionalString
to a piece of code that requires a non-optionalString
. Type safety helps you catch and fix errors as early as possible in the development process.
其他一些编程语言也有通用的 option types :例如,Haskell 中的 Maybe,Rust 中的 option 和 C++ 中的 0x2518122133411742
在没有选项类型的编程语言中,通常使用特定的 optional 来表示没有有效值。例如,在 Objective-C 中,
?
("sentinel" value)表示缺少对象。对于诸如 nil
之类的原始类型,不能使用空指针,因此您需要一个单独的变量(例如 int
和 value: Int
)或指定的哨兵值(例如 120x1313455 或 123134355 )。这些方法容易出错,因为很容易忘记检查 isValid: Bool
或检查哨兵值。此外,如果选择特定值作为标记,则意味着它不能再被视为有效值。Swift 的
-1
等选项类型通过引入一个特殊的、单独的 INT_MIN
值(因此您不必指定哨兵值)来解决这些问题,并利用强类型系统,以便编译器可以帮助您记住检查 nil必要的。为什么我会收到“致命错误:在解开 Optional 值时意外发现 nil”?
为了访问一个可选的值(如果它有一个),你需要 解包 它。可选值可以安全地或强制地解包。如果您强制解包一个可选项,并且它没有值,您的程序将因上述消息而崩溃。
Xcode 将通过突出显示一行代码来显示崩溃。问题出现在这条线上。
null pointer
使用两种不同的强制解包可能会发生这种崩溃:
1. 显式强制展开
这是通过可选的
isValid
运算符完成的。例如:let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Fatal error: Unexpectedly found nil while unwrapping an Optional value
由于
Optional
在这里是 nil
,因此您将在强制打开它的行上崩溃。2. 隐式解包的可选项
这些是用
!
定义的,而不是类型后的 anOptionalString
。var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
假定这些选项包含一个值。因此,无论何时您访问一个隐式解包的可选项,它都会自动为您强制解包。如果它不包含值,它将崩溃。print(optionalDouble) // <- CRASH
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
为了找出导致崩溃的变量,您可以在单击的同时按住 ⌥ 以显示定义,您可能会在其中找到可选类型。
特别是 IBOutlets,通常是隐式解包的选项。这是因为您的 xib 或 Storyboard将在初始化后在运行时链接导出。因此,您应该确保在加载之前没有访问 socket 。您还应该检查 Storyboard/xib 文件中的连接是否正确,否则在运行时这些值将是
nil
,因此在它们被隐式时会崩溃解开。修复连接时,尝试删除定义 socket 的代码行,然后重新连接它们。我什么时候应该强制打开一个 Optional ?
显式强制展开
作为一般规则,您永远不应该使用
!
运算符显式强制解包可选项。在某些情况下,使用 ?
是可以接受的——但只有当你 100% 确定可选包含一个值时,你才应该使用它。虽然在某些情况下您可以使用强制解包,但正如您所知的一个可选项包含一个值的事实 - 没有一个地方您不能安全地解包该可选项。
隐式展开的可选项
这些变量的设计使您可以将它们的分配推迟到代码的后面。在您访问它们之前,您有责任确保它们具有值(value)。然而,因为它们涉及强制展开,所以它们本质上仍然是不安全的——因为它们假设你的值是非 nil,即使赋值 nil 是有效的。
作为最后的手段,您应该只使用隐式解包的选项。如果您可以使用 ,或为变量提供 lazy variable - 您应该这样做,而不是使用隐式解包的可选项。
但是,有一个 default value ,您仍然可以使用下面列出的各种方法来安全地解包它们 - 但您应该始终谨慎使用它们。
我怎样才能安全地处理Optionals?
检查可选项是否包含值的最简单方法是将其与
nil
进行比较。if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
然而,在使用可选项时,99.9% 的情况下,您实际上想要访问它包含的值,如果它包含一个的话。为此,您可以使用可选绑定(bind)。可选绑定(bind)
Optional Binding 允许你检查一个 optional 是否包含一个值——并允许你将解包的值分配给一个新的变量或常量。它使用语法
!
或 !
,具体取决于您是否需要在绑定(bind)后修改新变量的值。例如:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
它的作用是首先检查可选项是否包含一个值。如果是这样,那么“解包”值将分配给一个新变量( nil
)——然后您可以自由使用它,就好像它是非可选的一样。如果可选项不包含值,则将调用 else 子句,如您所料。可选绑定(bind)的巧妙之处在于您可以同时解开多个可选绑定(bind)。您可以只用逗号分隔语句。如果所有可选项都被解包,该语句将成功。
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!")
}
在 if 语句中使用可选绑定(bind)的唯一问题是您只能从语句的范围内访问解包的值。如果您需要从语句范围之外访问值,您可以使用保护语句。few scenarios where implicitly unwrapped optionals are beneficial 允许您定义成功的条件——当前作用域只有在满足该条件时才会继续执行。它们使用语法
if let x = anOptional {...}
定义。因此,要将它们与可选绑定(bind)一起使用,您可以这样做:
guard let number = anOptionalInt else {
return
}
(请注意,在保护体中,您 必须 使用 guard statement 之一才能退出当前执行代码的范围)。如果
if var x = anOptional {...}
包含一个值,它将被解包并分配给新的 number
常量。守卫之后的代码将继续执行。如果它不包含值 - 守卫将执行括号内的代码,这将导致控制权转移,从而不会执行紧随其后的代码。关于guard语句的真正巧妙之处在于解包的值现在可用于语句后面的代码中(因为我们知道 future 的代码只有在可选值具有值时才能执行)。这对于消除通过嵌套多个 if 语句创建的 control transfer statements 非常有用。
例如:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Guards 还支持与 if 语句支持的相同的巧妙技巧,例如同时解开多个可选项和使用 guard condition else {...}
子句。是否使用 if 或 guard 语句完全取决于将来的代码是否需要可选项来包含值。
Nil 合并运算符
‘pyramids of doom’ 是 Nil Coalescing Operator 的一个漂亮的速记版本,主要用于将可选项转换为非可选项。它的语法为
anOptionalInt
,其中 number
是可选类型, where
与 a ?? b
的类型相同(尽管通常是非可选的)。它本质上让你说“如果
a
包含一个值,打开它。如果没有,则返回 b
”。例如,您可以像这样使用它:let number = anOptionalInt ?? 0
这将定义一个 a
类型的 a
常量,它将包含 b
的值,如果它包含一个值,或者 number
否则。它只是以下的简写:
let number = anOptionalInt != nil ? anOptionalInt! : 0
可选链您可以使用 ternary conditional operator 来调用方法或访问可选的属性。这只需在使用时为变量名添加
Int
后缀即可完成。例如,假设我们有一个变量
anOptionalInt
,类型为可选的 0
实例。var foo : Foo?
如果我们想在 ?
上调用一个不返回任何内容的方法,我们可以简单地做:foo?.doSomethingInteresting()
如果 foo
包含一个值,则将在其上调用此方法。如果没有,就不会发生什么坏事——代码将继续执行。(这类似于在 Objective-C 中向
Foo
发送消息的行为)因此,这也可用于设置属性以及调用方法。例如:
foo?.bar = Bar()
同样,如果 foo
是 foo
,这里不会发生任何不好的事情。您的代码将继续执行。可选链可以让你做的另一个巧妙的技巧是检查设置属性或调用方法是否成功。您可以通过将返回值与
nil
进行比较来做到这一点。(这是因为在不返回任何内容的方法上,可选值将返回
foo
而不是 nil
)例如:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
但是,在尝试访问属性或调用返回值的方法时,事情变得有点棘手。因为 nil
是可选的,所以从它返回的任何东西也是可选的。为了解决这个问题,您可以使用上述方法之一解包返回的选项——或者在访问方法或调用返回值的方法之前解包 Void?
本身。此外,顾名思义,您可以将这些语句“链接”在一起。这意味着如果
Void
有一个可选属性 foo
,它有一个属性 foo
– 你可以写如下:let optionalQux = foo?.baz?.qux
同样,因为 foo
和 baz
是可选的,从 qux
返回的值将始终是可选的,无论 foo
本身是否是可选的。baz
和 qux
一个经常未被充分利用的可选功能是能够使用 qux
和 map
函数。这些允许您将非可选转换应用于可选变量。如果可选项具有值,则可以对其应用给定的转换。如果它没有值,它将保持为 flatMap
。例如,假设您有一个可选字符串:
let anOptionalString:String?
通过对它应用 map
函数——我们可以使用 flatMap
函数将它连接到另一个字符串。因为
nil
接受一个非可选字符串参数,所以我们不能直接输入我们的可选字符串。但是,通过使用 map
,如果 stringByAppendingString
具有值,我们可以使用允许使用 stringByAppendingString
。例如:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
但是,如果 map
没有值, stringByAppendingString
将返回 anOptionalString
。例如:var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
anOptionalString
的工作原理与 map
类似,除了它允许您从闭包体内返回另一个可选值。这意味着您可以将可选输入到需要非可选输入的流程中,但可以输出可选本身。nil
Swift 的错误处理系统可以安全地与 Optional Chaining 一起使用:do {
let result = try someThrowingFunc()
} catch {
print(error)
}
如果 flatMap
抛出错误,该错误将被安全地捕获在 map
块中。您在
try!
块中看到的 someThrowingFunc()
常量尚未由我们声明 - 它是由 catch
自动生成的。您也可以自己声明
error
,它的优点是能够将其转换为有用的格式,例如:do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
以这种方式使用 catch
是尝试、捕获和处理来自抛出函数的错误的正确方法。还有
catch
吸收错误:if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
但是 Swift 的错误处理系统也提供了一种使用 error
来“强制尝试”的方法:let result = try! someThrowingFunc()
这篇文章中解释的概念也适用于这里:如果抛出错误,应用程序将崩溃。如果你能证明它的结果永远不会在你的上下文中失败,你应该只使用
try
- 这非常罕见。大多数情况下,您将使用完整的 Do-Try-Catch 系统 - 以及可选的
try?
,在处理错误并不重要的极少数情况下。资源
关于swift - "Fatal error: Unexpectedly found nil while unwrapping an Optional value"是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32170456/