“致命错误:意外地发现零,而解包可选值”是什么意思?

我的Swift程序崩溃了EXC_BAD_INSTRUCTION和这个错误。 这是什么意思,我该如何解决?

致命错误:在解包可选值时意外发现nil


这篇文章是为了收集“意外发现无”问题的答案,所以它们不是零散的,很难找到。 随意添加自己的答案或编辑现有的维基答案。


这个答案是社区维基。 如果你觉得它可以做得更好,随时编辑它!

背景:什么是可选的?

在Swift中, Optional是一个泛型类型,可以包含一个值(任何类型)或者根本没有值。

在许多其他编程语言中,通常使用特定的“标记”值来指示缺少值。 例如,在Objective-C中, nil (空指针)表示缺少一个对象。 但是,在使用原始类型时,这会变得更加棘手 - 应该用-1来指示缺少整数,或者可能是INT_MIN或其他整数? 如果选择任何特定值表示“无整数”,则表示不能再将其视为有效值。

Swift是一种类型安全的语言,这意味着该语言可以帮助您清楚您的代码可以使用的值的类型。 如果你的代码的一部分需要一个字符串,那么类型安全就会阻止你错误地向它传递一个Int。

在Swift中, 任何类型都可以选择 。 可选值可以取原始类型的任何值,也可以取特殊值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与Objective-C中的nil ,在Objective-C中, nil没有有效的对象指针;在Swift中,Optionals不限于对象/引用类型,可选行为与Haskell的行为相似也许。)


为什么我得到“致命错误:意外地发现零,而解包可选值”?

为了访问可选的值(如果它有一个值),你需要打开它。 可选值可以安全或强制解包。 如果你强制解开一个可选项,并且它没有值,你的程序将会崩溃并显示上述消息。

Xcode会通过突出显示一行代码来向您显示崩溃。 该行发生问题。

坠毁的线

这种崩溃可能发生在两种不同类型的解压缩之上:

1.显式强制解包

这是与! 操作员可选。 例如:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

由于anOptionalStringnil这里,你会得到你在哪里强制解开它的行崩溃。

2.隐式解包选项

这些都是用一个! ,而不是? 类型之后。

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

这些可选项假定包含一个值。 因此,无论何时访问隐式解包的可选项,它都会自动为您解压缩。 如果它不包含值,则会崩溃。

print(optionalDouble) // <- CRASH

为了确定哪个变量导致崩溃,您可以按住⌥,同时单击以显示定义,您可以在其中找到可选类型。

特别是IBOutlets通常是隐含的解包选择权。 这是因为在初始化之后,xib或storyboard会在运行时连接插座。 因此,您应该确保在加载之前不要访问插座。还应该检查storyboard / xib文件中的连接是否正确,否则这些值在运行时将nil ,因此在隐式解开。


我什么时候应该强制解包一个可选的?

显式强制解包

作为一般规则,你永远不应该明确强制使用! 运营商。 有可能会使用! 是可以接受的 - 但你应该只使用它,如果你100%确定可选包含一个值。

虽然可能有场合可以使用强制展开,但正如你所知道的,可选项包含一个值 - 没有一个地方你不能安全地解开那个可选项。

隐式解包选项

这些变量的设计是为了让您可以将它们的任务推迟到您的代码中。 在您访问它之前,您有责任确保它们具有价值。 然而,因为它们涉及到解锁力量,所以它们本质上仍然是不安全的 - 因为它们假定你的价值不是零,即使指定零是有效的。

作为最后的手段,你应该只使用隐含的解包选项。 如果你可以使用一个懒惰的变量,或者为变量提供一个默认值 - 你应该这样做,而不是使用隐式解包的可选。

但是,有几种情况下,隐式解包选项是有益的,您仍然可以使用各种方法安全地解开它们(如下所示) - 但您应该始终谨慎使用它们。


我如何安全地处理Optionals?

检查可选项是否包含值的最简单方法是将其与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 ) - 然后你可以自由使用,就好像它是非可选的。 如果可选项不包含值,则将按照您的预期调用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常量。 守卫之后的代码将继续执行。 如果它不包含值 - 警卫将执行括号内的代码,这将导致控制权的转移,以便后面的代码不会被执行。

关于guard语句的真正简洁的事情是解包后的值现在可以在语句后面的代码中使用(因为我们知道只有当可选值有值时才能执行将来的代码)。 这对于消除嵌套多个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是一个可选的类型和b是相同的类型a (尽管通常非可选)。

它基本上让你说“如果a包含一个值,解开它。 如果不是,则返回b “。 例如,你可以像这样使用它:

let number = anOptionalInt ?? 0

这将定义一个Int类型的number常量,它将包含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

同样,因为foobaz是可选的,所以qux返回的值总是可选的,而不管qux本身是否可选。

mapflatMap

可选功能的一个常用功能是使用mapflatMap函数的功能。 这些允许您将非可选变换应用于可选变量。 如果可选项具有值,则可以对其应用给定的转换。 如果它没有价值,它将保持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块中看到的error常量没有被我们声明 - 它是由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! 如果你能证明其结果永远不会在你的情况下失败 - 这是非常罕见的。

大多数情况下,您将使用完整的Do-Try-Catch系统 - 可选的系统,请try? ,在极少数情况下,处理错误并不重要。


资源

  • 关于Swift Optionals的Apple文档
  • 何时使用以及何时不使用隐式解包选项
  • 了解如何调试iOS应用程序崩溃

  • TL; DR答案

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

    避免使用!

    声明变量可选( ? ),而不是隐式解包选项(IUO)( !

    换句话说,宁可使用:
    var nameOfDaughter: String?

    代替:
    var nameOfDaughter: String!

    使用if letguard let可选变量

    要么像这样展开变量:

    if let nameOfDaughter = nameOfDaughter {
        print("My daughters name is: (nameOfDaughter)"
    }
    

    或者像这样:

    guard let nameOfDaughter = nameOfDaughter else { return }
    print("My daughters name is: (nameOfDaughter)"
    

    这个答案旨在简明扼要,全面理解阅读接受的答案


    这个问题出现在所有一切的时间 。 这是Swift新开发人员奋斗的第一件事情之一。

    背景:

    Swift使用“选项”的概念来处理可能包含值的值,或者不包含值。 在其他语言如C中,您可能会将值存储在变量中以指示它不包含任何值。 但是,如果0是一个有效值,该怎么办? 然后你可以使用-1。 如果-1是一个有效值呢? 等等。

    Swift选项允许您设置任何类型的变量以包含有效值或无值。

    当你声明一个变量意味着(键入x或无值)时,在类型后面加上一个问号。

    一个可选的实际上是一个容器,而不是包含一个给定类型的变量,或者什么也不是。

    可选需要“展开”才能获取其中的值。

    “!” 操作员是一名“强力拆包”操作员。 它说:“相信我,我知道我在做什么,我保证当这段代码运行时,变量不会包含零。” 如果你错了,你会崩溃。

    除非你真的知道你在做什么,避免“!” 强制解包操作员。 它可能是开始Swift程序员崩溃的最大来源。

    如何处理可选项:

    处理更安全的期权有很多其他方式。 这里有一些(不是详尽的清单)

    您可以使用“可选绑定”或“如果让”来说“如果此可选包含值,则将该值保存到新的非可选变量中。如果可选不包含值,则跳过此if语句的正文”。

    这里是一个可选的绑定与我们的foo可选的例子:

    if let newFoo = foo //If let is called optional binding. {
      print("foo is not nil")
    } else {
      print("foo is nil")
    }
    

    请注意,您在使用可选投标时定义的变量仅在if语句正文中存在(仅在“范围内”)。

    或者,您可以使用guard语句,该语句允许您在变量为零时退出函数:

    func aFunc(foo: Int?) {
      guard let newFoo = input else { return }
      //For the rest of the function newFoo is a non-optional var
    }
    

    Guard语句是在Swift 2中添加的。Guard允许您通过代码保留“黄金路径”,并避免使用“if let”可选绑定导致的不断增加的嵌套ifs级别。

    还有一个叫做“零合并运算符”的构造。 它采用“optional_var ?? replacement_val”的形式。 它返回一个与可选数据中包含的数据类型相同的非可选变量。 如果可选包含nil,它会在“??”之后返回表达式的值 符号。

    所以你可以使用这样的代码:

    let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
    print("foo = (newFoo)")
    

    您也可以使用try / catch或guard错误处理,但通常上述其他技术之一更清晰。

    编辑:

    另一个更微妙的选择权问题是“隐含的解包选择权。当我们声明富时,我们可以说:

    var foo: String!
    

    在这种情况下,foo仍然是可选的,但您不必拆开它来引用它。 这意味着任何时候你尝试引用foo,如果它是零,你就会崩溃。

    所以这段代码:

    var foo: String!
    
    
    let upperFoo = foo.capitalizedString
    

    即使我们不强制展开foo,也会引用foo的大写字母属性。 印刷看起来不错,但事实并非如此。

    因此,您希望对隐式解包选项非常小心。 (甚至在完全理解可选项之前甚至完全避免它们。)

    底线:当你第一次学习Swift时,假装“!” 字符不是该语言的一部分。 这很可能会让你陷入麻烦。

    链接地址: http://www.djcxy.com/p/87747.html

    上一篇: What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?

    下一篇: how can i access IBOutlet in another class? in swift