我的Swift程序崩溃与EXC_BAD_INSTRUCTION和以下类似错误之一。这个错误是什么意思,我该如何修复它?

致命错误:在打开可选值时意外地发现nil

or

致命错误:在隐式地展开可选值时意外地发现nil


这篇文章旨在收集“意外发现为零”问题的答案,这样它们就不会分散而难以找到。请随意添加您自己的答案或编辑现有的wiki答案。


当前回答

这是一个更重要的注释,这就是为什么隐式打开可选选项在调试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
}

其他回答

TL;博士回答

除了极少数例外,这条规则是黄金法则:

避免使用!

声明变量是可选的(?),而不是隐式地打开可选的(IUO) (!)

换句话说,应该使用: var nameOfDaughter:字符串?

而不是: var nameOfDaughter:字符串!

使用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)")

这个答案是简洁的,为了充分理解阅读接受的答案


资源

避免强制展开

首先,您应该知道什么是Optional值。 详细信息请参见《Swift编程语言》。

其次,您应该知道可选值有两个状态。一个是全值,另一个是空值。因此,在实现一个可选值之前,应该检查它是哪个状态。

你可以用if let…或者守卫让…Else等等。

还有一种方法,如果你不想在实现之前检查变量的状态,你也可以使用var buildingName = buildingName ??“buildingName”。

如果在CollectionView中出现此错误,请尝试创建CustomCell文件和Custom xib。

在mainVC的ViewDidLoad()中添加此代码。

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")

这个问题在《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时,假装“!”字符不是语言的一部分。这很可能会给你带来麻烦。

当我试图从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
}

我希望这个答案能帮助那些有同样问题的人,因为我发现标记的答案对理解可选选项及其工作方式是很好的资源,但并没有直接解决问题本身。