我的Swift程序崩溃与EXC_BAD_INSTRUCTION和以下类似错误之一。这个错误是什么意思,我该如何修复它?
致命错误:在打开可选值时意外地发现nil
or
致命错误:在隐式地展开可选值时意外地发现nil
这篇文章旨在收集“意外发现为零”问题的答案,这样它们就不会分散而难以找到。请随意添加您自己的答案或编辑现有的wiki答案。
我的Swift程序崩溃与EXC_BAD_INSTRUCTION和以下类似错误之一。这个错误是什么意思,我该如何修复它?
致命错误:在打开可选值时意外地发现nil
or
致命错误:在隐式地展开可选值时意外地发现nil
这篇文章旨在收集“意外发现为零”问题的答案,这样它们就不会分散而难以找到。请随意添加您自己的答案或编辑现有的wiki答案。
当前回答
当我试图从prepareforsegue方法中设置outlet值时,我有过这样的错误:
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
}
我希望这个答案能帮助那些有同样问题的人,因为我发现标记的答案对理解可选选项及其工作方式是很好的资源,但并没有直接解决问题本身。
其他回答
This is because you are trying to use a value which can possible be nil, but you decided you don't want to have to check it, but instead assume its set when you uses it and define it as !, there are different philosophies on use of variable set as force unwrap, some people are against there use at all, I personal think they are ok for things that will crash all the time and are simple to reason about, usually references to resource, like outlets to xib files, or uses of images with you app that are part of your assets, if these are not set up properly, you app is going to crash straight away, for a very obvious reason, you can get into difficult when the order of objects being created can be uncertain, and trying to reason solutions to this can be difficult, it usually means a bad design as even it you make them optional, calls to you optional variable may not ever be executed, some projects can demand use of force unwraps for security reasons, things like banking apps, because they want the app to crash rather then continue to work in an unplanned way.
背景:什么是可选选项?
在Swift中,Optional<Wrapped>是一个选项类型:它可以包含原始类型("Wrapped")的任何值,或者根本没有值(特殊值nil)。可选值在使用之前必须展开。
Optional是一个泛型类型,这意味着Optional<Int>和Optional<String>是不同的类型——<>里面的类型称为Wrapped类型。实际上,Optional是一个有两种情况的enum: .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
可选选项是一种简单而强大的工具,可以在编写代码时表达您的假设。编译器可以使用这些信息来防止你犯错误。摘自Swift编程语言:
Swift是一种类型安全的语言,这意味着该语言可以帮助你清楚你的代码可以使用的值的类型。如果你的部分代码需要一个String,类型安全可以防止你错误地传递一个Int。同样,类型安全可以防止您意外地将可选String传递给需要非可选String的代码段。类型安全可以帮助您在开发过程中尽早捕获并修复错误。
其他一些编程语言也有通用的选项类型:例如,在Haskell中是Maybe,在Rust中是option,在c++ 17中是optional。
In programming languages without option types, a particular "sentinel" value is often used to indicate the absence of a valid value. In Objective-C, for example, nil (the null pointer) represents the lack of an object. For primitive types such as int, a null pointer can't be used, so you would need either a separate variable (such as value: Int and isValid: Bool) or a designated sentinel value (such as -1 or INT_MIN). These approaches are error-prone because it's easy to forget to check isValid or to check for the sentinel value. Also, if a particular value is chosen as the sentinel, that means it can no longer be treated as a valid value.
选项类型,如Swift的Optional,通过引入一个特殊的、单独的nil值(这样你就不必指定一个哨兵值)来解决这些问题,并利用强类型系统,这样编译器就可以帮助你在必要时检查nil。
为什么我得到“致命错误:在打开可选值时意外地发现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
为了找出导致崩溃的变量,您可以按住“”键,同时单击以显示定义,在这里您可能会找到可选类型。
IBOutlets, in particular, are usually implicitly unwrapped optionals. This is because your xib or storyboard will link up the outlets at runtime, after initialization. You should therefore ensure that you’re not accessing outlets before they're loaded in. You also should check that the connections are correct in your storyboard/xib file, otherwise the values will be nil at runtime, and therefore crash when they are implicitly unwrapped. When fixing connections, try deleting the lines of code that define your outlets, then reconnect them.
什么时候我应该强制打开一个可选选项?
显式力展开
作为一般规则,永远不要使用!命令显式强制打开可选对象。操作符。可能在某些情况下使用!是可以接受的-但是只有当您100%确定可选选项包含一个值时才应该使用它。
虽然可能在某些情况下您可以使用强制展开,因为您知道一个可选选项包含一个值—没有一个地方您不能安全地展开该可选选项。
隐式打开可选选项
这些变量的设计使您可以将它们的赋值推迟到代码的后面部分。在访问它们之前,您有责任确保它们具有价值。然而,因为它们涉及强制展开,所以它们本质上仍然是不安全的——因为它们假设您的值是非nil,即使赋值为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")
}
这样做的目的是首先检查可选项是否包含值。如果是这样,那么' unwrapped '值被分配给一个新变量(number) -然后你可以自由地使用它,就像它是非可选的一样。如果optional不包含值,那么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语句。
守卫语句允许您定义成功的条件——只有满足该条件,当前作用域才会继续执行。它们用语法保护条件else{…}定义。
所以,要使用可选的绑定,你可以这样做:
guard let number = anOptionalInt else {
return
}
(注意,在保护体中,必须使用其中一个控制转移语句才能退出当前执行代码的作用域)。
如果一个optionalint包含一个值,它将被解包装并分配给新的数字常量。警卫之后的代码将继续执行。如果它不包含值-守卫将执行括号内的代码,这将导致控制权的转移,因此紧随其后的代码将不会被执行。
关于guard语句的真正巧妙之处在于,现在可以在语句后面的代码中使用unwrapped值(我们知道,只有当optional有值时,未来的代码才能执行)。这对于消除嵌套多个if语句所产生的“末日金字塔”非常有用。
例如:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
守卫还支持if语句所支持的简洁技巧,例如同时展开多个可选选项并使用where子句。
是否使用if或guard语句完全取决于将来的代码是否要求可选参数包含值。
无合并运算符
Nil Coalescing Operator是三元条件操作符的一个漂亮的简写版本,主要用于将可选项转换为非可选项。它有语法a ??B,其中a是可选类型,B是与a相同的类型(尽管通常是非可选的)。
它本质上允许您说“如果a包含一个值,则展开它”。如果没有,则返回b”。例如,你可以这样使用它:
let number = anOptionalInt ?? 0
这将定义一个Int类型的数字常量,如果包含值,则包含anOptionalInt的值,否则为0。
它只是缩写:
let number = anOptionalInt != nil ? anOptionalInt! : 0
可选的链接
可以使用可选链接来调用方法或访问可选对象上的属性。这可以通过在变量名后面加上?使用时。
例如,我们有一个变量foo,类型为可选的foo实例。
var foo : Foo?
如果我们想在foo上调用一个不返回任何东西的方法,我们可以简单地这样做:
foo?.doSomethingInteresting()
如果foo包含一个值,这个方法将被调用。如果没有,也不会发生什么糟糕的事情——代码将继续执行。
(这类似于在Objective-C中向nil发送消息)
因此,这也可以用于设置属性以及调用方法。例如:
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,如果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块中看到的错误常数并不是我们声明的——它是由catch自动生成的。
你也可以自己声明错误,它的优点是能够将其转换为有用的格式,例如:
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!如果你能证明它的结果在你的环境中永远不会失败——这是非常罕见的。
大多数情况下,你会使用完整的“尝试-捕捉”系统——还有一个可选的,试试?,在极少数情况下,处理错误并不重要。
资源
关于Swift optional的苹果文档 何时使用,何时不使用隐式展开的可选选项 学习如何调试iOS应用程序崩溃
当我试图从prepareforsegue方法中设置outlet值时,我有过这样的错误:
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
}
我希望这个答案能帮助那些有同样问题的人,因为我发现标记的答案对理解可选选项及其工作方式是很好的资源,但并没有直接解决问题本身。
首先,您应该知道什么是Optional值。 详细信息请参见《Swift编程语言》。
其次,您应该知道可选值有两个状态。一个是全值,另一个是空值。因此,在实现一个可选值之前,应该检查它是哪个状态。
你可以用if let…或者守卫让…Else等等。
还有一种方法,如果你不想在实现之前检查变量的状态,你也可以使用var buildingName = buildingName ??“buildingName”。
Swift 5.7 +
如果让简写的影子一个现有的可选变量
以上回答清楚地解释了为什么会出现这个问题以及如何处理这个问题。但是从swift 5.7+开始,有了一种新的方式来处理这个问题。
是我的变量:整数?
以前
if let myVariable = myVariable {
//this part get executed if the variable is not nil
}else{
//this part get executed if the variable is nil
}
now
现在我们可以省略表达式的右边。
if let myVariable {
//this part get executed if the variable is not nil
}else{
//this part get executed if the variable is nil
}
以前,我们必须重复引用标识符两次,这可能导致这些可选绑定条件过于冗长,特别是在使用冗长的变量名时。
但是现在有一种简写语法,可以通过省略表达式的右边来实现可选绑定。
同样的事情也适用于guard let语句。
详情如下:
if-let简写的建议