“致命错误:在解开可选值时意外发现nil”是什么意思?

佐手、 提交于 2020-02-28 07:50:30

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

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


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


#1楼

这个答案是社区维基如果您认为它可以做得更好,请随时对其进行编辑

背景:什么是可选的?

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

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

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

在Swift中, 任何类型都可以设为optional 。 可选的值可以从原始类型,任何值特殊值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中,Optionals不限于对象/引用类型。Optional的行为类似于Haskell的行为也许 。)


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

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

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

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

1.显式展开力

这是用! 可选的运算符。 例如:

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

由于anOptionalString在此处nil ,因此您在强制展开它的行上会发生崩溃。

2.隐式展开的可选

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

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

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

print(optionalDouble) // <- CRASH

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

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


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

显式展开力

通常,永远不要用!强制强行打开可选项! 操作员。 在某些情况下,可能会使用! 是可以接受的,但只有在100%确定可选值包含一个值的情况下,才应使用它。

虽然可能有某种情况下可以使用武力展开,你也知道对于一个可选包含一个值一个事实 -没有一个单一的地方,你不能安全地打开那可选替代。

隐式展开的可选

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

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

但是,在某些情况下,隐式展开的可选内容是有好处的 ,并且您仍然可以使用下面列出的各种方式来安全地展开它们-但始终应谨慎使用。


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

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

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

但是,使用可选参数时,实际上99.9%的时间都需要访问它包含的值(如果它包含一个值)。 为此,您可以使用Optional Binding

可选装订

可选绑定允许您检查可选项是否包含值–并允许您将展开的值分配给新变量或常量。 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语句

保护性语句使您可以定义成功的条件–只有满足该条件,当前作用域才会继续执行。 它们用语法guard condition else {...}

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

guard let number = anOptionalInt else {
    return
}

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

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

关于保护语句的真正精妙之处在于,展开后的值现在可以在该语句后的代码中使用(因为我们知道,将来的代码只能在可选值具有值的情况执行)。 这对于消除通过嵌套多个if语句而创建的“厄运金字塔”非常有用

例如:

guard let number = anOptionalInt else {
    return
}

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

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

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

无合并运算符

Nil合并运算符三元条件运算符的精简版本,主要用于将可选选项转换为非可选选项。 它具有语法a ?? b a ?? b ,其中a是一个可选的类型和b是相同的类型a (尽管通常非可选)。

从本质上讲,您可以说“如果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返回的值将始终是可选的。

mapflatMap

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

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

let anOptionalString:String?

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

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

try!

Swift的错误处理系统可以与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是尝试,捕获和处理来自throwing函数的错误的正确方法。

还有try? 吸收错误:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

但是Swift的错误处理系统还提供了一种通过try来“强制尝试”的方法try!

let result = try! someThrowingFunc()

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

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

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


资源资源


#2楼

这个问题出现在SO 所有的时间 。 这是新的Swift开发人员面临的第一件事。

背景:

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

使用Swift可选控件,您可以设置任何类型的变量以包含有效值或不包含任何值。

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

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

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

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

除非你真的知道你在做什么,避免“!” 强制解开操作符。 对于新手Swift程序员来说,这可能是最大的崩溃源。

如何处理可选项:

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

您可以使用“可选绑定”或“ if let”来表示“如果此可选包含值,则将该值保存到一个新的非可选变量中。如果可选不包含值,请跳过此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使您可以保留代码中的“黄金路径”,并避免嵌套的ifs级别不断提高,而嵌套的ifs有时是由于使用“ if let”可选绑定而导致的。

还有一个称为“零合并运算符”的构造。 它采用“ optional_var ?? replace_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时,请假装“!” 字符不是语言的一部分。 可能会惹上您的麻烦。


#3楼

首先,您应该知道什么是可选值。 您可以进入The Swift Programming Launage

有关详细信息。

其次,您应该知道可选值具有两个状态。 一个是全值,另一个是零值。 因此,在实现可选值之前,应检查它的状态。

您可以使用if let ...guard let ... else等等。

另一种方式,如果您不想在实现之前检查其状态,则还可以使用var buildingName = buildingName ?? "buildingName" 改为使用var buildingName = buildingName ?? "buildingName"


#4楼

TL; DR答案

除了极少数例外 ,此规则是黄金:

避免使用!

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

换句话说,请使用:
var nameOfDaughter: String?

代替:
var nameOfDaughter: String!

使用if letguard let解开可选变量

像这样解开变量:

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

或像这样:

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

该答案旨在简明扼要, 以便全面理解已接受的答案


#5楼

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

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

var i : Optional<Int>

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

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

因此,给变量“ i”分配零。 我们可以做var i = Optional<Int>.none或分配一个值,我们将传递一些值var i = Optional<Int>.some(28)

斯威夫特认为,“零”是价值的缺失。 并创建一个以nil初始化的实例,我们必须遵守一个称为ExpressibleByNilLiteral的协议,如果您猜对了,那就太好了,只有Optionals符合ExpressibleByNilLiteral并且不鼓励与其他类型兼容。

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

即使自己有包(没有双关语意)我的头周围选配:d 快乐Swfting所有

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!