fatal error: unexpectedly found nil while unwrapping an Optional value
这篇文章旨在收集“意外发现的nil”问题的答案,所以他们不分散,很难找到。随意添加自己的答案或edit现有的wiki答案。
背景:什么是可选?
在Swift中,Optional
是一个generic type,它可以包含一个值(任何类型),或者根本没有值。
在许多其他编程语言中,特定的“标记”值通常用于指示缺少值。在Objective-C中,例如,nil(null pointer)表示缺少对象。但是当使用原语类型时,这会变得更棘手 – 应该使用-1来表示缺少整数,或者INT_MIN,还是其他整数?如果选择任何特定值表示“无整数”,则意味着它不能再被视为有效值。
Swift是一种类型安全的语言,这意味着语言可以帮助你清楚你的代码可以使用的值的类型。如果你的代码的一部分期望一个字符串,类型安全防止你错误地传递它。
在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中,Optionals不限于对象/引用类型,可选行为类似于Haskell Maybe.)
为什么我得到“致命错误:意外发现nil而解包可选值”?
为了访问可选的值(如果它有一个),你需要打开它。可选值可以安全或强制解包。如果你强制解开一个可选的,它没有一个值,你的程序将崩溃与上述消息。
Xcode将通过突出显示一行代码来显示崩溃。问题出现在这一行。
这种崩溃可能发生在两种不同的力展开:
显式力展开
这是用!运算符。例如:
let anOptionalString: String? print(anOptionalString!) // <- CRASH
因为anOptionalString在这里是零,你会得到一个崩溃在你强制解开它的行。
2.隐式解包可选
这些是用一个!而不是一个?后类型。
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
这些可选项假定包含一个值。因此,只要你访问一个隐式解包的可选,它将自动强制解开你。如果它不包含一个值,它会崩溃。
print(optionalDouble) // <- CRASH
为了找出哪个变量导致崩溃,您可以按住⌥,同时单击以显示定义,您可以在其中找到可选类型。
特别地,IBOutlet通常是隐含地展开的可选项。这是因为在初始化之后,你的xib或storyboard会在运行时连接插座。因此,您应该确保您在加载之前不访问插座。您还应该检查storyboard / xib文件中的连接是否正确,否则值在运行时将为nil,因此当它们隐式时会崩溃展开。
我应该何时强制解开可选?
显式力展开
作为一般规则,你不应该明确强制解开一个可选的!运算符。可能有些情况下使用!是可以接受的 – 但是你应该只使用它,如果你100%确定可选包含一个值。
虽然可能有一种情况,你可以使用force unwrapping,你知道一个可选的包含一个值的事实 – 没有一个地方,你不能安全地打开该可选的代替。
隐含已解包可选
这些变量的设计使您可以推迟他们的分配,直到后来的代码。您有责任在访问它们之前确保它们具有值。然而,因为它们涉及力展开,它们仍然是不安全的 – 因为他们认为你的值是非零,即使赋值nil是有效的。
您应该只使用隐式未包装的可选值作为最后手段。如果你可以使用lazy variable,或者为变量提供一个default value,你应该这样做,而不是使用隐式解包的可选。
然而,有一个few scenarios where implicitly unwrapped optionals are beneficial,你仍然能够使用各种方法安全地打开他们,如下所列 – 但你应该总是使用它们在适当的谨慎。
如何安全地处理Optionals?
检查可选内容是否包含值的最简单的方法是将其与nil进行比较。
if anOptionalInt != nil { print("Contains a value!") } else { print("Doesn’t contain a value.") }
但是,使用可选项时,99.9%的时间,你实际想要访问它包含的值,如果它包含一个。为此,您可以使用可选绑定。
可选绑定
可选绑定允许您检查可选的是否包含值 – 并允许您将展开的值分配给新的变量或常量。它使用语法如果let x = anOptional {…}或者如果var x = anOptional {…},这取决于你需要修改绑定后的新变量的值。
例如:
if let number = anOptionalInt { print("Contains a value! It is \(number)!") } else { print("Doesn’t contain a number") }
这是首先检查可选的包含一个值。如果是,则’unwrapped’值被赋值给一个新的变量(数字),然后您可以自由使用它,就像它是非可选的。如果可选项不包含值,那么将调用else子句,如您所期望的。
什么是可选绑定的整洁,是你可以解开多个可选的同时。您可以使用逗号分隔语句。如果所有可选项都已解包,语句将成功。
var anOptionalInt : Int? var anOptionalString : String? if let number = anOptionalInt,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") }
另一个巧妙的技巧是,在解包之后,您可以使用where子句检查值上的某个条件。
if let number = anOptionalInt where number > 0 { print("anOptionalInt contains a value: \(number),and it’s greater than zero!") }
在if语句中使用可选绑定的唯一catch是您只能在语句的作用域内访问解包的值。如果需要从语句范围之外访问值,可以使用guard语句。
guard statement允许您定义成功的条件 – 并且当前范围将仅在满足该条件的情况下继续执行。它们用语法保护条件else {…}定义。
因此,要使用可选绑定,您可以这样做:
guard let number = anOptionalInt else { return }
(请注意,在防护主体中,您必须使用control transfer statements之一才能退出当前执行代码的范围)。
如果anOptionalInt包含一个值,它将被展开并赋值给新的数字常量。保护后的代码将继续执行。如果它不包含一个值 – 防护将执行括号内的代码,这将导致控制的转移,使得紧接在之后的代码将不会被执行。
关于guard语句的真正的事情是解开的值现在可以在语句之后的代码中使用(因为我们知道未来的代码只有在可选值有值时才能执行)。这是一个伟大的消除‘pyramids of doom’通过嵌套多个if语句创建。
例如:
guard let number = anOptionalInt else { return } print("anOptionalInt contains a value,and it’s: /(number)!")
Guard还支持与if语句支持的相同的整齐的技巧,例如同时解开多个可选并使用where子句。
是否使用if或guard语句完全取决于任何未来的代码是否需要可选的包含一个值。
无合并运算符
Nil Coalescing Operator是一个漂亮的ternary conditional operator的速记版本,主要设计用于将可选项转换为非可选项。它有语法a ?? b,其中a是可选类型,b是与a相同的类型(尽管通常是非可选的)。
它本质上让你说“如果一个包含一个值,解开它。如果它不然然后返回b“。例如,你可以这样使用它:
let number = anOptionalInt ?? 0
这将定义一个Int类型的数字常量,如果它包含一个值,它将包含anOptionalInt的值,否则为0。
它只是简写:
let number = anOptionalInt != nil ? anOptionalInt! : 0
可选链接
您可以使用Optional Chaining为了调用方法或访问可选的属性。这是简单地通过在变量名后面加一个?当使用它。
例如,假设我们有一个变量foo,类型为可选的Foo实例。
var foo : Foo?
如果我们想在foo上调用一个不返回任何东西的方法,我们可以这样做:
foo?.doSomethingInteresting()
如果foo包含一个值,则会调用此方法。如果没有,没有什么不好会发生 – 代码将简单地继续执行。
(这是类似的行为发送消息到nil在Objective-C)
foo?.bar = Bar()
再次,如果foo是nil,这里不会发生什么。您的代码将继续执行。
可选链接允许你做的另一个巧妙的技巧是检查设置属性或调用方法是否成功。你可以通过比较返回值为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
同样,由于foo和bar是可选的,因此从qux返回的值总是可选的,而不管qux本身是否是可选的。
地图和flatMap
经常未充分利用的可选功能是使用map和flatMap函数的能力。这些允许您将非可选变换应用于可选变量。如果一个可选项有一个值,你可以应用一个给定的转换。如果它没有值,它将保持为零。
例如,假设您有一个可选字符串:
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,除了它允许您从闭包体内返回另一个可选。这意味着您可以将一个可选输入到需要非可选输入的进程中,但可以输出可选输入。
尝试!
Swift的错误处理系统可以安全地用于Do-Try-Catch:
do { let result = try someThrowingFunc() } catch { print(error) }
如果someThrowingFunc()抛出一个错误,错误将被安全地捕获在catch块。
在catch块中看到的错误常量没有被我们声明 – 它是由catch自动生成的。
您也可以自己声明错误,它的优点是能够将其转换为有用的格式,例如:
do { let result = try someThrowingFunc() } catch let error as NSError { print(error.debugDescription) }
使用try这种方式是尝试,捕获和处理来自throwing函数的错误的正确方法。
还有尝试?吸收误差:
if let result = try? someThrowingFunc() { // cool } else { // handle the failure,but there's no error information available }
但Swift的错误处理系统也提供了一种方式来“尝试”尝试!
let result = try! someThrowingFunc()
在这篇文章中解释的概念也适用于这里:如果抛出一个错误,应用程序将崩溃。
你应该只使用try!如果你可以证明它的结果永远不会在你的上下文中失败 – 这是非常罕见的。
大多数时候,你将使用完整的Do-Try-Catch系统和可选的,在极少数情况下处理错误并不重要。
资源
> Apple documentation on Swift Optionals
> When to use and when not to use implicitly unwrapped optionals
> Learn how to debug an iOS app crash