问题:"致命错误:在展开可选值时意外发现nil"是什么意思?

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

致命错误:解开可选值时意外发现nil

致命错误:意外地发现nil,同时隐式展开一个可选值


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

标签:swift,exception,error-handling

回答1:

此答案是社区Wiki 。如果您认为它可以做得更好,请随时对其进行编辑

背景:什么是可选的?

在Swift中, 可选 通用类型,其中可以包含价值(任何形式),或者根本没有价值。

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

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

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

可选选项的类型后缀为?后缀

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

nil nil表示可选内容中缺少值:

anOptionalInt = nil

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


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

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

Xcode将通过突出显示一行代码来向您显示崩溃。该行出现问题。

此崩溃可能发生在两种不同类型的强制展开中:

1。显式展开力

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

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

致命错误:解开可选值时意外发现nil

由于anOptionalString在此处为nil,因此您在强制解开包装的那一行会崩溃。

2。隐式解包的可选内容

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

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

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

print(optionalDouble) // <- CRASH

致命错误:在隐式展开一个可选值

时意外发现nil

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

尤其是

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


我什么时候应该强制拆开选配件?

显式展开力

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

虽然可能存在 的情况,但您可以使用强制展开,如您所知的事实,可选内容包含一个值–没有 一个单独的地方,在那里您不能安全地打开该可选项目。

隐式展开的可选项

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

您应该只将隐式解包的可选内容用作最后的选择。如果可以使用惰性变量,或提供变量的默认值 –您应该这样做,而不是使用隐式展开的可选变量。

但是,在很少有方案中隐式解开的可选方案很有用,您仍然可以使用各种方式安全地包装它们,如下所示-但您应始终谨慎使用它们。


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

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

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

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

可选绑定

可选绑定允许您检查可选对象是否包含值–并允许您将展开的值分配给新变量或常量。它使用语法ifletx=anOptional{...}ifvarx=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语句

A 后卫声明允许您定义成功条件–只有满足该条件,当前作用域才会继续执行。它们使用语法guardconditionelse{...}定义。

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

guard let number = anOptionalInt else {
    return
}

(请注意,在防护体内,您必须使用控制传输语句,以退出当前正在执行的代码的范围)。

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

关于保护语句的真正精巧之处在于,现在可以在该语句之后的代码中使用未包装的值(因为我们知道,将来的代码只能(可选)具有值才能执行)。这对于消除通过嵌套多个if语句而创建的"厄运金字塔" 很有用。

例如:

guard let number = anOptionalInt else {
    return
}

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

Guard还支持if语句支持的相同技巧,例如同时展开多个可选对象并使用where子句。

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

无合并运算符

零合并运算符三元条件运算符,主要用于将可选内容转换为非可选内容。它具有语法a??b ,其中a是可选类型,b是与a相同的类型(尽管通常是非可选的)。 / p>

从本质上讲,您可以说"如果a包含一个值,则将其解包。如果没有,则返回b"。例如,您可以这样使用它:

let number = anOptionalInt ?? 0

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

它只是以下简称:

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

可选链接

您可以使用可选链接,以便调用方法或访问可选属性。只需在使用时在变量名后加上?即可。

例如,假设我们有一个变量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返回的值始终是可选的本身是可选的。

地图flatMap

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

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

let anOptionalString:String?

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

由于stringByAppendingString使用非可选字符串参数,因此我们无法直接输入可选字符串。但是,通过使用map,如果anOptionalString具有值,我们可以使用允许使用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类似,不同之处在于,它允许您从闭包体内返回另一个可选。这意味着您可以将可选项输入到需要非可选输入的过程中,但是可以输出可选项本身。

尝试!

Swift的错误处理系统可以与进行尝试捕获

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的错误处理系统还提供了一种使用try!

来"强制尝试"的方法。
let result = try! someThrowingFunc()

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

您只能使用try!,如果您可以证明其结果在您的上下文中永远不会失败-这是非常罕见的。

大多数情况下,在处理错误并不重要的情况下,您将使用完整的Do-Try-Catch系统-以及可选的try?


资源

回答2:

TL; DR答案

对于很少有例外,此规则很重要:

避免使用!

声明变量为可选(?),而不是隐式解包的可选(IUO)(!)

换句话说,请使用:
varnameOfDaughter:String?

代替:
varnameOfDaughter:字符串!

使用ifletguardlet

打开可选变量

像这样解开变量:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

或者这样:

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

该答案旨在简明扼要,获取完整的理解,请阅读接受的答案

回答3:

关于此问题,所有时间都会出现。这是新的Swift开发人员首先要面对的事情。

背景:

Swift使用"可选"的概念来处理可能包含值或不包含值的值。在其他语言(例如C)中,您可能会在变量中存储值0,以指示该变量不包含任何值。但是,如果0是有效值怎么办?然后,您可以使用-1。如果-1是有效值怎么办?依此类推。

Swift可选参数使您可以设置任何类型的变量以包含有效值或不包含任何值。

当您声明要表示的变量(类型x,或没有值)时,您要在类型之后加上问号。

实际上是一个可选容器,它包含给定类型的变量或不包含任何变量。

一个可选的选项需要被"解开",以获取内部的值。

"!"运算符是"强制展开"运算符。它说:"相信我。我知道我在做什么。我保证在这段代码运行时,该变量将不包含nil。"如果输入错误,则会崩溃。

除非您真的知道,否则请避免使用"!"强制解开操作符。对于初次使用Swift的程序员来说,这可能是最大的崩溃源。

如何处理可选内容:

还有许多其他处理可选选项的方法,这些方法更安全。这里有一些(不是详尽的清单)

您可以使用"可选绑定"或"如果让"来表示"如果此可选包含值,则将该值保存到新的非可选变量中。如果可选不包含值,则跳过主体这个if语句"。

以下是带有foo可选选项的可选绑定的示例:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

请注意,您在使用可选出价时定义的变量仅在if语句的正文中存在(仅在"范围内")。

或者,您可以使用保护语句,如果变量为nil,则可以退出函数:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

在Swift 2中添加了Guard语句。Guard使您可以在代码中保留"黄金路径",并避免有时由于使用" if let"可选绑定而导致的嵌套ifs不断增加的水平。

还有一个称为"零合并运算符"的构造。它采用的形式为" optional_var ?? replacement_val"。它返回一个非可选变量,其类型与可选变量中包含的数据相同。如果可选包含nil,则返回" ??"之后的表达式的值符号。

因此您可以使用如下代码:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

您还可以使用try / catch或guard错误处理,但通常上述其他技术之一更干净。

编辑:

另一个带有可选内容的微妙陷阱是"隐式展开的可选内容。当我们声明foo时,我们可以说:

var foo: String!

在这种情况下,foo仍然是可选的,但是您不必解开包装即可引用它。这意味着您每次尝试引用foo时,如果它为nil,就会崩溃。

所以这段代码:

var foo: String!


let upperFoo = foo.capitalizedString

即使我们没有强制展开foo,引用foo的capitalizedString属性也会崩溃。打印看起来不错,但不是。

因此,您要非常谨慎地使用隐式展开的可选内容。 (甚至可能完全避免使用它们,直到您对可选项有了深刻的了解。)

底线:第一次学习Swift时,请假装"!"字符不是语言的一部分。可能会惹上您的麻烦。

回答4:

由于以上答案清楚地说明了如何使用Optionals安全演奏。我将尽力解释一下什么是真正的Optional。

声明可选变量的另一种方法是

vari:可选

可选类型不过是具有两种情况的枚举,即

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

因此要给变量'i'分配零。我们可以执行vari=Optional .none 或分配一个值,我们将传递一些值vari=Optional .some(28) < / p>

根据迅速,"无"是指没有价值。并创建以nil初始化的实例,我们必须遵守称为ExpressibleByNilLiteral的协议,如果您猜对了,那就太棒了,只有Optionals符合<不鼓励使用code> ExpressibleByNilLiteral 并遵循其他类型。

ExpressibleByNilLiteral有一个名为init(nilLiteral:)的单一方法,该方法用nil初始化一个实例。通常,您不会调用此方法,并且根据快速文档,不建议在每次使用nil文字初始化Optional类型时都直接调用此初始化方法,因为编译器会调用它。

甚至我自己也不得不把头缠住(不要双关语):D 快乐地挥舞一切

回答5:

首先,您应该知道什么是Optional值。您可以转到 Swift编程语言详细信息。

第二,您应该知道可选值具有两种状态。一个是完整值,另一个是nil值。因此,在实现可选值之前,应检查它处于哪种状态。

您可以使用iflet...guardlet...else等。

另一种方法,如果您不想在实现之前检查变量状态,还可以使用varbuildingName=buildingName??改为使用" buildingName"

回答6:

当我尝试通过prepare for segue方法设置我的Outlets值时,出现了一次错误:

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
}

我希望此答案可以帮助所有遇到相同问题的人,因为我发现标记的答案是理解可选项及其工作方式的宝贵资源,但并未直接解决问题本身。

回答7:

通过告诉编译器相信您那里永远不会存在nil值,从而允许您的应用进行编译,基本上,您尝试在Swift只允许非nil值的地方使用nil值。

有几种情况会导致这种致命错误:

  1. 强制展开:

    let user = someVariable!
    

    如果someVariable为nil,则会崩溃。通过强制解包,您将nil检查责任从编译器移到了您,基本上是通过强制强制解包,您向编译器保证了那里永远没有nil值。猜猜如果在someVariable中以零结尾的情况下会出现什么情况?

    解决方案?使用可选的绑定(aka if-let),在那里进行变量处理:

    if user = someVariable {
        // do your stuff
    }
    
  2. 强制(向下)广播:

    let myRectangle = someShape as! Rectangle
    

    通过强制强制转换,您告诉编译器不再担心,因为您将始终具有Rectangle实例。只要这样,您就不必担心。当您或您的同事从项目开始循环非矩形值时,问题就开始了。

    解决方案?使用可选的绑定(aka if-let),在那里进行变量处理:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
    
  3. 隐式展开的可选。假设您具有以下类定义:

    class User {
        var name: String!
    
        init() {
            name = "(unnamed)"
        }
    
        func nicerName() {
            return "Mr/Ms " + name
        }
    }
    

    现在,如果没人将name属性设置为nil,则可以按预期工作,但是,如果User是从缺少name键的JSON初始化的,则在尝试使用属性。

    解决方案?请勿使用它们:)除非您有102%的权限确保该属性在需要使用时始终具有非null值。在大多数情况下,可以转换为可选或非可选。将其设置为非可选还将使编译器通过告诉您缺少的代码路径为该属性赋值来帮助您

  4. 未连接或尚未连接的插座。这是方案3的特例。基本上,您有要使用的XIB加载类。

    class SignInViewController: UIViewController {
    
        @IBOutlet var emailTextField: UITextField!
    }
    

    现在,如果您错过了通过XIB编辑器连接插座的操作,则应用程序将在您崩溃后立即崩溃。我要使用电源插座。解?确保所有插座均已连接。或对它们使用?运算符:emailTextField?.text="my@email.com"。或将插座声明为可选,尽管在这种情况下,编译器将迫使您在整个代码中对其进行包装。

  5. 来自Objective-C的值,并且没有可空性注释。假设我们具有以下Objective-C类:

    @interface MyUser: NSObject
    @property NSString *name;
    @end
    

    现在,如果未指定可空性注释(显式或通过NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END),然后name属性将在Swift中作为String!导入(IUU-隐式展开的可选)。一旦某些快速代码想要使用该值,如果name为nil,它将崩溃。

    解决方案?在您的Objective-C代码中添加可空性注释。但是请注意,就可空性而言,Objective-C编译器有点宽容,即使您明确将它们标记为nonnull

回答8:

错误EXC_BAD_INSTRUCTION致命错误:当您声明@IBOutlet时,意外发现nil,同时隐式展开一个可选值 ,但未连接到故事板

您还应该了解其他答案中提到的 Optionals 的工作方式,但这是我第一次看到的唯一内容。

回答9:

这更是一个重要的注释,这就是为什么在调试nil值时隐式解开的可选对象可能具有欺骗性。

考虑以下代码:编译时没有错误/警告:

c1.address.city = c3.address.city

但是在运行时却出现以下错误:致命错误:在展开可选值时意外发现nil

您能告诉我哪个对象是nil吗?

你不能!

完整代码为:

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
}

长话短说,使用varaddress:地址!,您正在隐藏来自其他读者的变量可能为nil的可能性。而当它崩溃时,您就像是"到底是什么?!我的地址不是可选的,所以我为什么要崩溃?!。

因此最好这样写:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

您现在可以告诉我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
}

回答10:

我在从表视图控制器切换到视图控制器时遇到了这个错误,因为我忘记在主故事板上为视图控制器指定自定义类名称。

一些简单的事情值得检查,如果其他一切看起来还不错

回答11:

如果您在CollectionView中遇到此错误,请尝试同时创建CustomCell文件和Custom xib。

将此代码添加到mainVC的ViewDidLoad()中。

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
回到顶部