我的迅速程序正在崩溃EXC_BAD_INSTRUCTION
以及以下类似错误之一。这个错误是什么意思,我该如何解决?
致命错误:在解开可选值时出乎意料地发现了零
或者
致命错误:在隐式解开可选值时意外发现了无
^这篇文章旨在收集"意外发现"问题的答案,以使它们不会分散且难以找到。随时添加自己的答案或编辑现有的Wiki答案。^
答案
背景:什么是可选的?
在斯威夫特中,Optional<Wrapped>
是一个选项类型:它可以包含来自原始(“包装”)类型的任何值,也可以完全没有值(特殊值nil
)。unwrapped在使用之前。
可选的是通用类型, 意思就是Optional<Int>
和Optional<String>
是不同的类型 - 内部类型<>
称为包装类型。在引擎盖下,可选是枚举有两种情况:.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
选项是一个简单而强大的工具,可以在编写代码时表达您的假设。编译器可以使用此信息来防止您犯错误。从快速编程语言:
斯威夫特是一个类型安全 语言,这意味着该语言可以帮助您清除代码可以使用的值类型。如果您的代码的一部分需要一个
String
,类型安全可防止您将其传递给Int
因为失误。**Likewise, type safety prevents you from accidentally passing an optionalString
to a piece of code that requires a non-optionalString
.**类型安全可帮助您在开发过程中尽早捕获和解决错误。
其他一些编程语言也有通用期权类型: 例如,或许在哈斯克尔,选项在生锈,然后选修的在C ++ 17中。
在编程语言中没有 选项类型,特定“哨兵"值通常用于指示没有有效值的情况。例如,在Objective-C中,nil
(这空指针) 表示缺少对象。int
,无法使用零指针,因此您需要一个单独的变量(例如value: Int
和isValid: Bool
)或指定的前哨价值(例如-1
或者INT_MIN
)。这些方法容易出错,因为很容易忘记检查isValid
或检查哨兵值。同样,如果选择特定值作为前哨,则意味着它不再被视为有效的价值。
选项类型,例如Swift的Optional
通过引入一个特殊的,独立的解决这些问题nil
值(因此您不必指定哨兵值),并且通过利用强类型系统,以便编译器可以帮助您记住在必要时检查零。
为什么我得到”致命错误:在解开可选值时出乎意料地发现了零"?
为了访问可选的价值(如果它完全有),您需要unwrap 它。可选的值可以安全或强行解开。如果您强制使用可选的,那就可以了没有如果有一个值,您的程序将崩溃并显示上述消息。
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
致命错误:意外发现无implicitly解开可选价值
为了确定导致崩溃的变量,您可以保持⌥在单击以显示定义时,您可能会在其中找到可选类型。
尤其是IBOUTLETS通常是隐式未包装的选项。这是因为您的XIB或故事板将在运行时链接插座,后 初始化。nil
在运行时,因此,当它们被隐式解开时崩溃。修复连接时,请尝试删除定义您的插座的代码行,然后重新连接它们。
我什么时候应该强制解开可选的?
明确的力解开
通常,您绝对不应明确强制将可选的!
操作员。可能在某些情况下使用!
是可以接受的 - 但是,只有100%确定可选的值包含一个值,才应该使用它。
在那里时可能 如您所知事实 可选的包含一个值 - 没有单身的放置您无法安全解开该可选的地方。
隐式解开选项
这些变量的设计是这样,您可以将其分配推迟到代码后期。这是你的 在您访问它们之前,有责任确保它们具有价值。但是,由于它们涉及武力解开,所以它们仍然天生不安全 - 因为它们认为即使分配零是有效的,您的价值是非nil的。
您应该仅将隐式未包装的选项用作最后一招 。如果您可以使用懒变量,或提供默认值对于变量 - 您应该这样做,而不是使用隐式未包装的可选。
但是,有一个很少有隐式未包装选项有益的场景,您仍然能够使用各种安全解开它们的方法如下所述 - 但是您应该总是谨慎使用它们。
如何安全处理选项?
检查可选的值是否包含值的最简单方法是将其比较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")
}
首先要检查可选的值。如果它做 ,然后将"未包装"值分配给新变量(number
) - 然后,您可以自由使用,就好像它是非可行的一样。如果是可选的没有包含一个值,然后将调用其他子句,如您所期望的那样。
可选绑定的整洁是您可以同时解开多个选项。您可以将语句与逗号分开。如果所有选项都拆开,则该声明将成功。
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语句中使用可选绑定的唯一捕获是,您只能从语句范围内访问未包装值。如果您需要从语句范围外部访问该值,则可以使用后卫声明。
A后卫声明允许您定义成功的条件 - 当前范围只有在满足该条件时才会继续执行。它们是用语法定义的guard condition else {...}
。
因此,要将它们与可选的绑定一起使用,您可以做到这一点:
guard let number = anOptionalInt else {
return
}
^(请注意,在警卫队内,您must 使用之一控制传输语句为了退出当前执行代码的范围)。^
如果anOptionalInt
包含一个值,它将被解开并分配给新的number
持续的。代码后后卫将继续执行。如果它不包含值 - 警卫将在括号内执行代码,这将导致控制转移,以使之后的代码不会被执行。
关于后卫语句的真正整洁的事情是,未包装的值现在可以在遵循该语句的代码中使用(我们知道未来代码可以仅有的 如果可选的值)执行。这非常适合消除“厄运金字塔”通过嵌套多个if语句创建。
例如:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it's: \(number)!")
警卫还支持IF语句支持的相同的整洁技巧,例如同时解开多个选项并使用where
条款。
您是否使用IF或Guard语句完全取决于是否将来任何代码需要可选的包含值。
零合并运营商
这无合并运算符是一个漂亮的简写版本三元条件操作员,主要是为了将选项转换为非选项。它具有语法a ?? b
, 在哪里a
是可选类型,b
与a
(虽然通常是非选项)。
从本质上讲,您可以说:“如果a
包含一个值,解开它。如果没有返回b
反而”。例如,您可以这样使用:
let number = anOptionalInt ?? 0
这将定义一个number
常数Int
类型,要么包含anOptionalInt
,如果它包含一个值,或0
否则。
只是速记:
let number = anOptionalInt != nil ? anOptionalInt! : 0
可选的链接
您可以使用可选的链接为了调用方法或访问可选的属性。这只是通过用一个后缀变量名来完成的?
使用时。
例如,说我们有一个变量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
和baz
是可选的,价值从qux
不管是否是否qux
本身是可选的。
map
和flatMap
通常没有使用的功能,可选的功能是能够使用map
和flatMap
功能。这些允许您将非选项转换应用于可选变量。如果可选的值有一个值,则可以将给定的转换应用于其。如果没有价值,它将保留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
flatMap
工作类似map
,除了允许您返回其他可选的闭合主体。这意味着您可以将可选的过程输入到需要非选项输入但可以输出可选本身的过程中。
try!
Swift的错误处理系统可以安全地与做法:
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!
如果您可以证明其结果永远不会在您的上下文中失败 - 这是非常罕见的。
大多数情况下,您将使用完整的操作方法 - 以及可选的系统,try?
,在极少数情况下处理错误并不重要。