我的Swift程序崩溃,EXC_BAD_INSTRUCTION
并出现以下类似错误之一。此错误是什么意思,我该如何解决?
致命错误:展开一个可选值时意外发现nil
要么
致命错误:意外发现nil,同时隐式展开Optional值
这篇文章旨在收集“意外发现零”问题的答案,以使它们不会分散且很难找到。随意添加您自己的答案或编辑现有的Wiki答案。
我的Swift程序崩溃,EXC_BAD_INSTRUCTION
并出现以下类似错误之一。此错误是什么意思,我该如何解决?
致命错误:展开一个可选值时意外发现nil
要么
致命错误:意外发现nil,同时隐式展开Optional值
这篇文章旨在收集“意外发现零”问题的答案,以使它们不会分散且很难找到。随意添加您自己的答案或编辑现有的Wiki答案。
Answers:
这个答案是社区维基。如果您认为它可以做得更好,请随时对其进行编辑!
在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
与nil
Objective-C中的不同。在Objective-C中,nil
缺少有效的对象指针;在Swift中,Optional不限于对象/引用类型。Optional的行为类似于Haskell的Maybe。)
为了访问可选值(如果它有一个值),您需要将其拆开。可以安全或强制打开可选值。如果强行解开一个可选值,但它没有值,则程序将崩溃,并显示以上消息。
Xcode将通过突出显示一行代码来向您显示崩溃。在这条线上发生问题。
两种不同类型的强制展开会发生此崩溃:
这是通过!
操作员在可选件上完成的。例如:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
致命错误:展开一个可选值时意外发现nil
由于anOptionalString
是nil
在这里,你会得到你在哪里迫使展开它的行崩溃。
这些是在类型之后用!
而不是a 定义?
的。
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
假定这些可选参数包含一个值。因此,每当您访问隐式展开的可选内容时,它将自动为您强制展开。如果不包含值,它将崩溃。
print(optionalDouble) // <- CRASH
致命错误:意外发现nil,同时隐式展开Optional值
为了找出导致崩溃的变量,您可以⌥单击以显示定义,并在其中找到可选类型。
特别是IBOutlet,通常是隐式解包的可选。这是因为您的xib或情节提要在初始化后将在运行时连接出口。因此,您应该确保在装入插座之前不要访问插座。还应该检查故事板/ xib文件中的连接是否正确,否则值将nil
在运行时发生,因此在隐式展开时会崩溃。 。固定连接时,请尝试删除定义插座的代码行,然后重新连接它们。
作为一般规则,永远不要显式地强制用!
运算符解开可选选项。在某些情况下,!
可以接受使用–但只有在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语句完全取决于将来的任何代码是否要求可选变量包含值。
在无合并运算符是一个漂亮的简写版的三元条件操作,主要是为了自选转换为非选配。它具有语法a ?? b
,其中a
是可选类型,并且b
与a
(虽然通常是非可选的)类型相同。
从本质上讲,您可以说“如果a
包含值,则将其解包。如果没有,则返回b
”。例如,您可以这样使用它:
let number = anOptionalInt ?? 0
这将定义一个类型number
常量,如果包含值,则将包含的值,否则将包含。Int
anOptionalInt
0
它只是以下方面的简写:
let number = anOptionalInt != nil ? anOptionalInt! : 0
您可以使用可选链接来调用方法或访问可选属性。?
使用变量名时,只需在变量名后加上a即可。
例如,假设我们有一个变量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
具有值的情况下使用allow 。
例如:
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
块中。
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?
情况下,在处理错误并不重要的极少数情况下,您将使用完整的Do-Try-Catch系统-和可选的系统。
compactMap()
代替了flatMap()
。
除了极少数例外,此规则是黄金:
!
?
),而不是隐式展开的Optional(IUO)(!
)换句话说,请使用:
var nameOfDaughter: String?
代替:
var nameOfDaughter: String!
if let
或展开可选变量guard let
像这样解开变量:
if let nameOfDaughter = nameOfDaughter {
print("My daughters name is: \(nameOfDaughter)")
}
或像这样:
guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")
该答案旨在简明扼要,以便全面理解已接受的答案
这个问题来了所有的时间在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时,请假装“!” 字符不是语言的一部分。可能会惹上您的麻烦。
guard
子句什么都没有。与使用if var
一样有效的构造没有什么关系if let
。where
在讨论if let
绑定时,没有什么值得提及的子句(在许多情况下,它消除了整个嵌套层)。可选链接没有任何意义。
由于以上答案清楚地说明了如何使用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初始化一个实例。通常,您不会调用此方法,并且根据快速文档,不建议在使用nil
文字初始化Optional类型时,由于编译器会直接调用此初始值设定项。
即使自己有包(没有双关语意)我的头周围选配:d 快乐Swfting所有。
当我尝试按以下方式准备segue方法设置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
}
我希望这个答案可以帮助所有遇到相同问题的人,因为我发现标记的答案是理解可选项及其工作方式的宝贵资源,但并未直接解决问题本身。
updateView
目标控制器中调用的位置;)
updateView
,因为我使用的目标控制器不需要在这种情况下,name
变量设置nameLabel.text
的viewDidLoad
。但是,如果我们进行了大量设置,那么一定会更好地创建另一个函数并从中调用它viewDidLoad
。
基本上,您通过告诉编译器相信您那里永远不会有nil值,从而尝试让Swift允许非nil值的地方使用nil值,从而允许您的应用进行编译。
有几种情况会导致这种致命错误:
强制拆包:
let user = someVariable!
如果someVariable
为零,那么您将崩溃。通过强制解包,您将nil检查责任从编译器移到了您,基本上通过进行强制解包,您向编译器保证了那里永远不会有nil值。并猜测如果nil值以某种方式结尾会发生什么someVariable
?
解?使用可选的绑定(aka if-let),在那里进行变量处理:
if user = someVariable {
// do your stuff
}
强制(向下)广播:
let myRectangle = someShape as! Rectangle
在这里,通过强制转换可以使编译器不再担心,因为您将始终在其中拥有一个Rectangle
实例。只要这样,您就不必担心。当您或您的同事从项目开始循环非矩形值时,问题就开始了。
解?使用可选的绑定(aka if-let),在那里进行变量处理:
if let myRectangle = someShape as? Rectangle {
// yay, I have a rectangle
}
隐式展开的可选。假设您具有以下类定义:
class User {
var name: String!
init() {
name = "(unnamed)"
}
func nicerName() {
return "Mr/Ms " + name
}
}
现在,如果没有人通过将该name
属性设置为来弄乱该属性nil
,那么它将按预期工作,但是如果User
是从缺少该name
键的JSON初始化的,则在尝试使用该属性时会出现致命错误。
解?请勿使用它们:)除非您有102%的权限确保该属性在需要使用时始终具有非null值。在大多数情况下,可以转换为可选或非可选。使其变为非可选还将使编译器通过告诉您错过的代码路径为该属性赋值来帮助您
未连接或尚未连接的插座。这是方案#3的特例。基本上,您要使用一些XIB加载的类。
class SignInViewController: UIViewController {
@IBOutlet var emailTextField: UITextField!
}
现在,如果您错过了通过XIB编辑器连接插座的操作,则一旦您要使用插座,该应用程序就会崩溃。解?确保所有插座均已连接。或?
对它们使用运算符:emailTextField?.text = "my@email.com"
。或将插座声明为可选,尽管在这种情况下,编译器将迫使您在整个代码中对其进行包装。
来自Objective-C的值,并且没有可空性注释。假设我们有以下Objective-C类:
@interface MyUser: NSObject
@property NSString *name;
@end
现在,如果未指定任何可为空的注释(显式或通过NS_ASSUME_NONNULL_BEGIN
/ NS_ASSUME_NONNULL_END
),则该name
属性将在Swift中作为String!
(一个IUO-隐式展开的可选)导入。一旦某些快速代码想要使用该值,如果name
为nil ,它将崩溃。
解?在您的Objective-C代码中添加可空性注释。不过请注意,就可空性而言,Objective-C编译器有点宽容,即使您明确将它们标记为,也可能会得到nil值nonnull
。
这更是一个重要的注释,这就是为什么在调试nil
值时隐式解开的可选参数可能是欺骗性的。
考虑以下代码:编译时没有错误/警告:
c1.address.city = c3.address.city
但是在运行时,它会出现以下错误:致命错误:展开一个Optional值时意外地找到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
}
长话短说,通过使用var address : Address!
您隐藏了变量可能nil
来自其他读者的可能性。当它崩溃时,您就像“到底是什么?!我address
不是可选的,所以为什么我要崩溃?!。
因此最好这样写:
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
}
声明情节提要但未连接到情节提要时,错误EXC_BAD_INSTRUCTION
和fatal error: unexpectedly found nil while implicitly unwrapping an Optional value
出现次数最多。@IBOutlet
您还应该了解其他答案中提到的Optionals的工作方式,但这是对我来说唯一的一次。
@IBOutlet
该错误的原因是否不应该出现致命错误:意外地发现nil,同时隐式展开了该错误的可选值版本?