swift - “Fatal error: Unexpectedly found nil while unwrapping an Optional value”是什么意思?

标签 swift exception error-handling

我的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现有的Wiki答案。

最佳答案

这个答案是community wiki。如果您觉得可以做得更好,请随时edit it!
背景:什么是可选的?
在Swift中, Optional<Wrapped> option type:它可以包含原始(“Wrapped”)类型的任何值,或者根本不包含任何值(特殊值nil)。在使用可选值之前,必须先将其设置为展开后的
可选的是generic type,这意味着Optional<Int>Optional<String>是不同的类型-<>内部的类型称为Wrapped类型。在幕后,Optional是enum,有两种情况:.some(Wrapped).none,其中.none等于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 an Int by mistake. Likewise, type safety prevents you from accidentally passing an optional String to a piece of code that requires a non-optional String. Type safety helps you catch and fix errors as early as possible in the development process.


其他一些编程语言也具有通用的option types:例如,Haskell中的Maybe,Rust中的option和C++ 17中的optional
在没有选项类型的编程语言中,特定的"sentinel" value通常用于指示缺少有效值。例如,在Objective-C中,nil(null pointer)表示缺少对象。对于诸如int的原始类型,不能使用空指针,因此您将需要一个单独的变量(例如value: IntisValid: Bool)或一个指定的哨兵值(例如-1INT_MIN)。这些方法容易出错,因为很容易忘记检查isValid或检查前哨值。同样,如果选择特定值作为标记,则意味着它不再可以视为有效值。
诸如Swift的Optional之类的选项类型通过引入一个特殊的,单独的nil值(因此您不必指定哨兵值)以及利用强类型系统来解决这些问题,从而使编译器可以帮助您记住在以下情况下检查nil必要。

为什么会出现“致命错误:在展开可选值时意外发现nil”?
为了访问可选值(如果有的话),您需要解包。可选值可以安全地或强制地解包。如果强行解开一个可选值,但它没有值,则程序将崩溃,并显示以上消息。
Xcode将通过突出显示一行代码来向您显示崩溃。在这条线上发生问题。
crashed line
两种不同类型的强制展开会发生此崩溃:
1.显式展开力
这是通过!运算符(可选)完成的。例如:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH

Fatal error: Unexpectedly found nil while unwrapping an Optional value


由于此处的anOptionalStringnil,因此您在强制展开该行的行上会崩溃。
2.隐式展开的可选
这些是用!定义的,而不是在类型之后的?定义的。
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


为了找出导致崩溃的变量,您可以在单击以显示定义时按住⌥,在那里可以找到可选类型。

特别是IBOutlet,通常是隐式解包的可选。这是因为在初始化后,您的xib或 Storyboard 将在运行时链接导出。因此,您应该确保在装入插座之前不访问插座。还应该检查 Storyboard/xib文件中的连接是否正确,否则值在运行时将为nil,因此在隐式包含它们时会崩溃解开。固定连接时,请尝试删除定义插座的代码行,然后重新连接它们。

我什么时候应该强制解开Optional?
显式展开力
通常,永远不要使用!运算符显式强制打开可选项。在某些情况下,可以接受使用!-但只有在100%确定可选值包含一个值的情况下,才应使用nil
虽然有时您可以使用强制展开,但众所周知,一个可选内容包含一个值,但在一个地方,您无法安全地展开该可选内容。

隐式展开的可选
设计这些变量是为了使您可以将它们的分配推迟到以后的代码中。您有责任在访问它们之前确保它们具有值(value)。但是,由于它们涉及强制展开,因此它们本质上仍然是不安全的,因为即使您指定nil有效,它们也会假定您的值不为nil。
您应仅将隐式解包的可选内容用作最后的选择。如果可以使用lazy variable或为变量提供default value,则应这样做,而不是使用隐式解包的可选变量。
但是,有一个few scenarios where implicitly unwrapped optionals are beneficial,您仍然可以使用各种方法来安全地解开它们,如下所示-但是您应始终谨慎使用它们。

我该如何安全处理Optionals?
检查可选参数是否包含值的最简单方法是将其与if let x = anOptional {...}进行比较。
if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}
但是,在使用可选参数的情况下,实际上99.9%的时间都是要访问它包含的值(如果它包含一个值)。为此,您可以使用“可选绑定(bind)”。
可选装订
可选绑定(bind)允许您检查可选项是否包含值–并允许您将展开的值分配给新变量或常量。它使用语法if var x = anOptional {...}number,这取决于绑定(bind)后是否需要修改新变量的值。
例如:
if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}
首先,请检查可选内容是否包含值。如果是这样,则将“unwrapped”值分配给新变量(guard condition else {...})–然后,您可以自由使用它,就像它是非可选的一样。如果可选参数不包含值,则将按您期望的那样调用else子句。
可选绑定(bind)的整洁之处在于,您可以同时解开多个可选对象。您可以只用逗号分隔语句。如果所有 optional 均未包装,则该语句将成功。
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)的唯一陷阱是,您只能从该语句的范围内访问未包装的值。如果需要从语句范围之外访问该值,则可以使用保护语句。
guard statement允许您定义成功条件–只有满足该条件,当前作用域才会继续执行。它们使用语法anOptionalInt定义。
因此,要将它们与可选绑定(bind)一起使用,可以执行以下操作:
guard let number = anOptionalInt else {
    return
}
(请注意,在防护体内,您必须使用control transfer statements之一来退出当前正在执行的代码的范围)。
如果number包含一个值,它将被解包并分配给新的where常量。保护后的代码将继续执行。如果它不包含值,则守卫将执行方括号内的代码,这将导致控制权的转移,因此紧随其后的代码将不会执行。
关于保护语句的真正精妙之处是,展开后的值现在可以在该语句后的代码中使用(因为我们知道,将来的代码只能在可选值具有值的情况下执行)。这对于消除通过嵌套多个if语句创建的‘pyramids of doom’很有用。
例如:
guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")
guard 队还支持if语句支持的相同技巧,例如同时解开多个可选内容并使用a ?? b子句。
是否使用if或guard语句完全取决于将来的任何代码是否要求可选变量包含值。
无合并运算符
Nil Coalescing Operatorternary conditional operator的漂亮缩写形式,主要用于将可选内容转换为非可选内容。它具有语法a,其中b是可选类型,而aa是相同类型(尽管通常是非可选的)。
从本质上讲,您可以说“如果b包含一个值,则将其解包。如果没有,则返回number”。例如,您可以这样使用它:
let number = anOptionalInt ?? 0
这将定义一个Int类型的anOptionalInt常量,如果该常量包含一个值,则该常量将包含0的值,否则将包含?
它只是以下内容的简写:
let number = anOptionalInt != nil ? anOptionalInt! : 0
可选链接
您可以使用Optional Chaining来调用方法或访问可选属性。使用变量名时,只需在其后加上foo后缀即可。
例如,假设我们有一个Foo变量,其类型为可选foo实例。
var foo : Foo?
如果我们想在foo上调用不返回任何内容的方法,则只需执行以下操作:
foo?.doSomethingInteresting()
如果nil包含一个值,则将在其上调用此方法。如果没有,则不会发生任何不好的事情-代码将继续执行。
(这与向Objective-C中的foo发送消息类似)
因此,这也可以用于设置属性和调用方法。例如:
foo?.bar = Bar()
同样,如果nilnil,在这里也不会发生任何不良情况。您的代码将继续执行。
可选链接允许您执行的另一个巧妙技巧是检查设置属性或调用方法是否成功。您可以通过将返回值与Void?进行比较来做到这一点。
(这是因为在不返回任何内容的方法上,可选值将返回Void而不是foo)
例如:
if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}
但是,在尝试访问属性或调用返回值的方法时,事情变得有些棘手。因为foo是可选的,所以从它返回的任何内容也将是可选的。为了解决这个问题,您可以解开使用上述方法之一返回的 optional ,也可以在访问返回值的方法或调用返回值的方法之前解开foo本身。
同样,顾名思义,您可以将这些语句“链接”在一起。这意味着,如果baz具有可选属性qux,该属性具有foo属性–您可以编写以下内容:
let optionalQux = foo?.baz?.qux
同样,因为bazqux是可选的,所以从qux返回的值将始终是可选的,而不管map本身是否是可选的。flatMapmap可选功能经常使用不足的功能是可以使用flatMapnil函数。这些允许您将非可选转换应用于可选变量。如果可选值具有值,则可以对其应用给定的转换。如果没有值,它将保留为map
例如,假设您有一个可选的字符串:
let anOptionalString:String?
通过对其应用stringByAppendingString函数,我们可以使用stringByAppendingString函数将其连接到另一个字符串。
由于map采用非可选字符串参数,因此我们无法直接输入可选字符串。但是,通过使用stringByAppendingString,如果anOptionalString具有值,我们可以使用允许使用anOptionalString
例如:
var anOptionalString:String? = "bar"

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // Optional("foobar")
但是,如果map没有值,则nil将返回flatMap。例如:
var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil
maptry!相似,但它允许您从闭包主体中返回另一个可选内容。这意味着您可以将可选项输入到需要非可选输入的过程中,但是可以输出可选项本身。someThrowingFunc()Swift的错误处理系统可以与Do-Try-Catch一起安全地使用:
do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}
如果catch引发错误,该错误将被安全地捕获在error块中。
您尚未在我们声明的catch块中看到的catch常量-它是由error自动生成的。
您也可以自己声明try,它具有将其转换为有用格式的优点,例如:
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的错误处理系统还提供了一种使用try!进行“强制尝试”的方法:
let result = try! someThrowingFunc()
这篇文章中解释的概念也适用于此:如果引发错误,则应用程序将崩溃。
仅当可以证明try?的结果在您的上下文中不会失败的情况下,才应使用ojit_code-这是非常罕见的。
大多数情况下,在处理错误并不重要的极少数情况下,您将使用完整的Do-Try-Catch系统-以及可选的ojit_code系统。

资源资源
  • Apple documentation on Swift Optionals
  • When to use and when not to use implicitly unwrapped optionals
  • Learn how to debug an iOS app crash
  • 关于swift - “Fatal error: Unexpectedly found nil while unwrapping an Optional value”是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52667793/

    相关文章:

    ios - 在已经出现后更改动态高度 UITableViewCell 的高度

    ios - 我应该在tableView中写什么(_ :cellForRowAtIndexPath:) method to custom cells?

    Swift 2.0 初始化嵌套字典无法按预期工作

    java - 如何使用 JAVA 从 Internet 下载 zip 文件并将其保存在特定文件夹中?

    exception - JavaFX 线程崩溃

    perl - Perl-自定义错误输出

    powershell - Powershell 函数中的错误处理

    swift - 在 Swift 中使用 return 而不是 break 退出循环有什么副作用吗?

    javascript - 从错误中恢复

    javascript - Jasmine javaScript 异常