我的Swift程序崩溃与EXC_BAD_INSTRUCTION和以下类似错误之一。这个错误是什么意思,我该如何修复它?
致命错误:在打开可选值时意外地发现nil
or
致命错误:在隐式地展开可选值时意外地发现nil
这篇文章旨在收集“意外发现为零”问题的答案,这样它们就不会分散而难以找到。请随意添加您自己的答案或编辑现有的wiki答案。
我的Swift程序崩溃与EXC_BAD_INSTRUCTION和以下类似错误之一。这个错误是什么意思,我该如何修复它?
致命错误:在打开可选值时意外地发现nil
or
致命错误:在隐式地展开可选值时意外地发现nil
这篇文章旨在收集“意外发现为零”问题的答案,这样它们就不会分散而难以找到。请随意添加您自己的答案或编辑现有的wiki答案。
当前回答
首先,您应该知道什么是Optional值。 详细信息请参见《Swift编程语言》。
其次,您应该知道可选值有两个状态。一个是全值,另一个是空值。因此,在实现一个可选值之前,应该检查它是哪个状态。
你可以用if let…或者守卫让…Else等等。
还有一种方法,如果你不想在实现之前检查变量的状态,你也可以使用var buildingName = buildingName ??“buildingName”。
其他回答
基本上,你试图在Swift只允许非nil值的地方使用nil值,通过告诉编译器信任你,那里永远不会有nil值,从而允许你的应用程序编译。
有几种情况会导致这种致命错误:
forced unwraps: let user = someVariable! If someVariable is nil, then you'll get a crash. By doing a force unwrap you moved the nil check responsibility from the compiler to you, basically by doing a forced unwrap you're guaranteeing to the compiler that you'll never have nil values there. And guess what it happens if somehow a nil value ends in in someVariable? Solution? Use optional binding (aka if-let), do the variable processing there: if user = someVariable { // do your stuff } forced (down)casts: let myRectangle = someShape as! Rectangle Here by force casting you tell the compiler to no longer worry, as you'll always have a Rectangle instance there. And as long as that holds, you don't have to worry. The problems start when you or your colleagues from the project start circulating non-rectangle values. Solution? Use optional binding (aka if-let), do the variable processing there: if let myRectangle = someShape as? Rectangle { // yay, I have a rectangle } Implicitly unwrapped optionals. Let's assume you have the following class definition: class User { var name: String! init() { name = "(unnamed)" } func nicerName() { return "Mr/Ms " + name } } Now, if no-one messes up with the name property by setting it to nil, then it works as expected, however if User is initialized from a JSON that lacks the name key, then you get the fatal error when trying to use the property. Solution? Don't use them :) Unless you're 102% sure that the property will always have a non-nil value by the time it needs to be used. In most cases converting to an optional or non-optional will work. Making it non-optional will also result in the compiler helping you by telling the code paths you missed giving a value to that property Unconnected, or not yet connected, outlets. This is a particular case of scenario #3. Basically you have some XIB-loaded class that you want to use. class SignInViewController: UIViewController { @IBOutlet var emailTextField: UITextField! } Now if you missed connecting the outlet from the XIB editor, then the app will crash as soon as you'll want to use the outlet. Solution? Make sure all outlets are connected. Or use the ? operator on them: emailTextField?.text = "my@email.com". Or declare the outlet as optional, though in this case the compiler will force you to unwrap it all over the code. Values coming from Objective-C, and that don't have nullability annotations. Let's assume we have the following Objective-C class: @interface MyUser: NSObject @property NSString *name; @end Now if no nullability annotations are specified (either explicitly or via NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END), then the name property will be imported in Swift as String! (an IUO - implicitly unwrapped optional). As soon as some swift code will want to use the value, it will crash if name is nil. Solution? Add nullability annotations to your Objective-C code. Beware though, the Objective-C compiler is a little bit permissive when it comes to nullability, you might end up with nil values, even if you explicitly marked them as nonnull.
这个问题在《SO》杂志上经常出现。这是Swift新开发人员首先要解决的问题之一。
背景:
Swift使用“可选项”的概念来处理可能包含值或不包含值的值。在C等其他语言中,可以在变量中存储0值,以表示它不包含任何值。但是,如果0是一个有效值呢?然后你可以用-1。如果-1是一个有效值怎么办?等等。
Swift可选选项允许您设置任何类型的变量,以包含有效值或无值。
当将变量声明为mean(类型x,或无值)时,在类型后加上一个问号。
可选对象实际上是一个容器,它要么包含给定类型的变量,要么什么都不包含。
为了获取其中的值,一个可选对象需要“解包装”。
“!”操作符是一个“强制展开”操作符。上面写着“相信我。我知道我在做什么。我保证当这段代码运行时,变量将不包含nil。”如果你错了,你就会崩溃。
除非您确实知道自己在做什么,否则请避免使用“!”强制展开操作符。对于初学者来说,它可能是最大的崩溃来源。
如何处理可选选项:
还有很多其他更安全的方法来处理可选选项。以下是一些(不是详尽的列表)
您可以使用“可选绑定”或“if let”来表示“如果这个可选包含一个值,则将该值保存到一个新的非可选变量中。”如果optional不包含值,则跳过该If语句的语句体”。
下面是一个使用foo optional进行可选绑定的例子:
if let newFoo = foo //If let is called optional binding. {
print("foo is not nil")
} else {
print("foo is nil")
}
注意,使用可选投标时定义的变量只存在于if语句体中(仅“在作用域内”)。
或者,你可以使用一个guard语句,它允许你在变量为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允许您在代码中保留“黄金路径”,并避免由于使用“if let”可选绑定而导致的不断增加的嵌套if级别。
还有一种构造叫做“nil合并运算符”。它的形式是“optional_var ??”replacement_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的capitalizedString属性时崩溃,即使我们没有强制展开foo。指纹看起来很好,但实际上不是。
因此,您要非常小心地使用隐式展开的可选项。(甚至可能完全避免它们,直到你对可选选项有了充分的理解。)
底线:当你第一次学习Swift时,假装“!”字符不是语言的一部分。这很可能会给你带来麻烦。
这是一个更重要的注释,这就是为什么隐式打开可选选项在调试nil值时可能具有欺骗性。
考虑下面的代码: 它编译时没有错误/警告:
c1.address.city = c3.address.city
然而在运行时,它给出以下错误:致命错误:在打开可选值时意外地发现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地址:地址!您隐藏了变量可以为nil的可能性,不让其他读取器看到。当它崩溃的时候,你会想“搞什么鬼?!”我的地址不是可选的,所以我为什么要崩溃?!
因此,最好这样写:
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
}
首先,您应该知道什么是Optional值。 详细信息请参见《Swift编程语言》。
其次,您应该知道可选值有两个状态。一个是全值,另一个是空值。因此,在实现一个可选值之前,应该检查它是哪个状态。
你可以用if let…或者守卫让…Else等等。
还有一种方法,如果你不想在实现之前检查变量的状态,你也可以使用var buildingName = buildingName ??“buildingName”。
Xcode 12 iOS 14 Swift 5
我的问题是导航类型,因为我直接调用vie控制器,而没有实例化故事板,这意味着数据还没有从故事板设置。
当你导航时,用
let homeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "home") as? HomeEventsViewController
homeViewController?.modalTransitionStyle = .crossDissolve
homeViewController?.modalPresentationStyle = .fullScreen
view.present(homeViewController ?? UIViewController(), animated: true, completion: nil)
希望它能起作用:-)