swift - “ fatal error :在展开可选值时意外发现nil”是什么意思?

标签 swift exception error-handling

我的Swift程序因EXC_BAD_INSTRUCTION和以下类似错误之一而崩溃。此错误是什么意思,我该如何解决?


  致命错误:展开一个可选值时意外发现nil


要么


  致命错误:意外发现nil,同时隐式展开Optional值




该帖子旨在收集“意外发现的零”问题的答案,以使它们不会分散且很难找到。随意添加您自己的答案或edit现有的Wiki答案。

最佳答案

答案是community wiki。如果您觉得可以做得更好,请随时edit it

背景:什么是可选的?

在Swift中,Optional是一个generic type,可以包含一个值(任何类型),或者根本不包含任何值。

在许多其他编程语言中,通常使用特定的“前哨”值来指示缺少值。例如,在Objective-C中,nilnull pointer)表示缺少对象。但这在处理原始类型时变得更加棘手-是否应使用-1来指示缺少整数,或者可能缺少INT_MIN或某些其他整数?如果选择任何特定值表示“无整数”,则意味着它不再可以视为有效值。

Swift是一种类型安全的语言,这意味着该语言可帮助您弄清代码可以使用的值的类型。如果代码的一部分需要一个字符串,则类型安全性可防止您误将其传递给Int。

在Swift中,任何类型都可以设为可选。可选值可以采用原始类型的任何值,即or特殊值nil

可选内容在类型上带有?后缀:

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided


nil表示可选项中缺少值:

anOptionalInt = nil


(请注意,此nil与Objective-C中的nil不同。在Objective-C中,nil是缺少有效的对象指针;在Swift中,Optional不仅限于对象/引用类型。Optional的行为类似于Haskell的Maybe。)



为什么会出现“致命错误:在展开可选值时意外发现nil”?

为了访问可选值(如果有的话),您需要将其解包。可以安全或强制打开可选值。如果强行解开一个可选值,但它没有值,则程序将崩溃,并显示以上消息。

Xcode将通过突出显示一行代码来向您显示崩溃。在这条线上发生问题。

crashed line

两种不同类型的强制展开会发生此崩溃:

1.显式展开力

这是通过!运算符(可选)完成的。例如:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH



  致命错误:展开一个可选值时意外发现nil


由于这里的anOptionalStringnil,因此在您强制展开包装的线上会发生崩溃。

2.隐式展开的可选

这些在类型之后用!而不是?定义。

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used


假定这些可选参数包含一个值。因此,每当您访问隐式解包的可选内容时,它将自动为您强制解包。如果不包含值,则将崩溃。

print(optionalDouble) // <- CRASH



  致命错误:意外发现nil,同时隐式展开Optional值


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



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



我什么时候应该强制解开Optional?

显式展开力

通常,永远不要使用!运算符显式强制打开可选选项。在某些情况下,使用!是可以接受的–但是,只有在100%确定可选值包含一个值的情况下,才应使用它。

虽然有时您可以使用强制展开,但众所周知,一个可选内容包含一个值,但在一个地方,您无法安全地展开该可选内容。



隐式展开的可选

设计这些变量是为了使您可以将它们的分配推迟到以后的代码中。您有责任在访问它们之前确保它们具有价值。但是,由于它们涉及强制展开,因此它们本质上仍然是不安全的,因为即使您指定nil有效,它们也会假定您的值不为nil。

您应仅将隐式解包的可选内容用作最后的选择。如果可以使用lazy variable或为变量提供default value –您应该这样做,而不是使用隐式展开的可选变量。

但是,有一个few scenarios where implicitly unwrapped optionals are beneficial,您仍然可以使用各种方法来安全地解开它们,如下所示-但是您应始终谨慎使用它们。



我如何安全地处理可选项目?

检查可选参数是否包含值的最简单方法是将其与nil进行比较。

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}


但是,使用可选选项时,实际上99.9%的时间都需要访问它包含的值(如果它包含一个值)。为此,您可以使用“可选绑定”。

可选装订

可选绑定允许您检查可选项是否包含值–并允许您将展开的值分配给新变量或常量。它使用语法if let x = anOptional {...}if var x = anOptional {...},这取决于绑定后是否需要修改新变量的值。

例如:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}


首先,请检查可选内容是否包含值。如果是这样,则将“ unwrapped”值分配给新变量(number)–然后,您可以自由使用它,就像它是非可选的一样。如果可选参数不包含值,则将按您期望的那样调用else子句。

可选绑定的整洁之处在于,您可以同时解开多个可选对象。您可以只用逗号分隔语句。如果所有可选选项均未包装,则该语句将成功。

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语句中使用可选绑定的唯一陷阱是,您只能从该语句的范围内访问未包装的值。如果需要从语句范围之外访问该值,则可以使用保护语句。

guard statement允许您定义成功条件–只有满足该条件,当前作用域才会继续执行。它们使用语法guard condition else {...}定义。

因此,要将它们与可选绑定一起使用,可以执行以下操作:

guard let number = anOptionalInt else {
    return
}


(请注意,在保护体内,必须使用control transfer statements之一以退出当前正在执行的代码的范围)。

如果anOptionalInt包含一个值,它将被解包并分配给新的number常量。保护后的代码将继续执行。如果它不包含值,则守卫将执行方括号内的代码,这将导致控制权的转移,因此紧随其后的代码将不会被执行。

关于保护语句的真正精巧之处在于,展开后的值现在可以在该语句后的代码中使用(因为我们知道,将来的代码只能在可选值具有值的情况下执行)。这对于消除通过嵌套多个if语句创建的‘pyramids of doom’很有用。

例如:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")


警卫队还支持if语句支持的相同技巧,例如同时展开多个可选内容并使用where子句。

是否使用if或guard语句完全取决于将来的任何代码是否要求可选值包含值。

无合并运算符

Nil Coalescing Operatorternary conditional operator的精简版本,主要用于将可选内容转换为非可选内容。它具有语法a ?? b,其中a是可选类型,ba是相同类型(尽管通常是非可选的)。

从本质上讲,您可以说“如果a包含值,则将其取消包装。如果没有,则返回b”。例如,您可以这样使用它:

let number = anOptionalInt ?? 0


这将定义number类型的Int常量,如果包含值,则该常量将包含anOptionalInt的值,否则将包含0

它只是以下内容的简写:

let number = anOptionalInt != nil ? anOptionalInt! : 0


可选链接

您可以使用Optional Chaining来调用方法或访问可选属性。使用变量名称时,只需在其后缀一个?后即可。

例如,假设我们有一个变量foo,类型为可选的Foo实例。

var foo : Foo?


如果我们想在foo上调用不返回任何内容的方法,则只需执行以下操作:

foo?.doSomethingInteresting()


如果foo包含一个值,则将在此方法上调用。如果没有,则不会发生任何不好的事情-代码将继续执行。

(这类似于在Objective-C中向nil发送消息)

因此,这也可以用于设置属性和调用方法。例如:

foo?.bar = Bar()


同样,如果foonil,在这里也不会发生任何不良情况。您的代码将继续执行。

可选链接允许您执行的另一种巧妙技巧是检查设置属性或调用方法是否成功。您可以通过将返回值与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


同样,因为foobaz是可选的,所以从qux返回的值将始终是可选的,而不管qux本身是否是可选的。

mapflatMap

带有可选功能的经常使用不足的功能是可以使用mapflatMap函数。这些允许您将非可选转换应用于可选变量。如果可选值具有值,则可以对其应用给定的转换。如果没有值,它将保留为nil

例如,假设您有一个可选的字符串:

let anOptionalString:String?


通过对它应用map函数,我们可以使用stringByAppendingString函数将其连接到另一个字符串。

由于stringByAppendingString采用非可选字符串参数,因此我们无法直接输入可选字符串。但是,通过使用map,如果stringByAppendingString具有值,我们可以使用允许使用anOptionalString

例如:

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


flatMapmap相似,不同之处在于它允许您从闭包主体中返回另一个可选内容。这意味着您可以将可选项输入到需要非可选输入的过程中,但可以输出可选项本身。

try!

Swift的错误处理系统可以与Do-Try-Catch一起安全使用:

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}


如果someThrowingFunc()引发错误,该错误将被安全地捕获在catch块中。

您在error块中看到的catch常量尚未由我们声明-由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的错误处理系统还提供了一种使用try!进行“强制尝试”的方法:

let result = try! someThrowingFunc()


这篇文章中解释的概念也适用于此:如果引发错误,则应用程序将崩溃。

仅当可以证明其结果在您的上下文中不会失败的情况下,才应使用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

关于swift - “ fatal error :在展开可选值时意外发现nil”是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39378807/

相关文章:

ios - 错误 : Optional(Alamofire. AFError.invalidURL (""))

java - 不能抛出 Exception 类型的异常,异常类型必须是 Throwable 的子类

C++ - 新手 - 简单的尝试/捕捉

java - Spring RestTemplate 交换抛出 UnhandledHttpStatusException

python - 从异常中捕获有关已执行代码行的信息

Swift 编译器错误组

ios - 不能继承 WKWebView

swift - SwiftUI 中的内容是什么?

error-handling - 如何处理PayPal智能按钮中的错误?

python - 将自定义函数输出添加到 Python 日志记录处理程序