swift – 什么是“致命错误:意外发现nil同时展开可选值”是什么意思?

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

fatal error: unexpectedly found nil while unwrapping an Optional value

这篇文章旨在收集“意外发现的nil”问题的答案,所以他们不分散,很难找到。随意添加自己的答案或edit现有的wiki答案。

这个答案是 community wiki.如果你觉得它可以做得更好,随意 edit it

背景:什么是可选?

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

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

Swift是一种类型安全的语言,这意味着语言可以帮助你清楚你的代码可以使用的值的类型。如果你的代码的一部分期望一个字符串,类型安全防止你错误地传递它。

在Swift中,任何类型都可以选择。可选值可以取原始类型的任何值,or特殊值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 Maybe.)

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

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

Xcode将通过突出显示一行代码来显示崩溃。问题出现在这一行。

这种崩溃可能发生在两种不同的力展开:

显式力展开

这是用!运算符。例如:

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

因为anOptionalString在这里是零,你会得到一个崩溃在你强制解开它的行。

2.隐式解包可选

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

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

这些可选项假定包含一个值。因此,只要你访问一个隐式解包的可选,它将自动强制解开你。如果它不包含一个值,它会崩溃。

print(optionalDouble) // <- CRASH

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

特别地,IBOutlet通常是隐含地展开的可选项。这是因为在初始化之后,你的xib或storyboard会在运行时连接插座。因此,您应该确保您在加载之前不访问插座。您还应该检查storyboard / xib文件中的连接是否正确,否则值在运行时将为nil,因此当它们隐式时会崩溃展开。

我应该何时强制解开可选?

显式力展开

作为一般规则,你不应该明确强制解开一个可选的!运算符。可能有些情况下使用!是可以接受的 – 但是你应该只使用它,如果你100%确定可选包含一个值。

虽然可能有一种情况,你可以使用force unwrapping,你知道一个可选的包含一个值的事实 – 没有一个地方,你不能安全地打开该可选的代替。

隐含已解包可选

这些变量的设计使您可以推迟他们的分配,直到后来的代码。您有责任在访问它们之前确保它们具有值。然而,因为它们涉及力展开,它们仍然是不安全的 – 因为他们认为你的值是非零,即使赋值nil是有效的。

您应该只使用隐式未包装的可选值作为最后手段。如果你可以使用lazy variable,或者为变量提供一个default value,你应该这样做,而不是使用隐式解包的可选。

然而,有一个few scenarios where implicitly unwrapped optionals are beneficial,你仍然能够使用各种方法安全地打开他们,如下所列 – 但你应该总是使用它们在适当的谨慎。

如何安全地处理Optionals?

检查可选内容是否包含值的最简单的方法是将其与nil进行比较。

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

但是,使用可选项时,99.9%的时间,你实际想要访问它包含的值,如果它包含一个。为此,您可以使用可选绑定。

可选绑定

可选绑定允许您检查可选的是否包含值 – 并允许您将展开的值分配给新的变量或常量。它使用语法如果let x = anOptional {…}或者如果var x = anOptional {…},这取决于你需要修改绑定后的新变量的值。

例如:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

这是首先检查可选的包含一个值。如果是,则’unwrapped’值被赋值给一个新的变量(数字),然后您可以自由使用它,就像它是非可选的。如果可选项不包含值,那么将调用else子句,如您所期望的。

什么是可选绑定的整洁,是你可以解开多个可选的同时。您可以使用逗号分隔语句。如果所有可选项都已解包,语句将成功。

var anOptionalInt : Int?
var anOptionalString : String?

if let number = anOptionalInt,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")
}

另一个巧妙的技巧是,在解包之后,您可以使用where子句检查值上的某个条件。

if let number = anOptionalInt where number > 0 {
    print("anOptionalInt contains a value: \(number),and it’s greater than zero!")
}

在if语句中使用可选绑定的唯一catch是您只能在语句的作用域内访问解包的值。如果需要从语句范围之外访问值,可以使用guard语句。

guard statement允许您定义成功的条件 – 并且当前范围将仅在满足该条件的情况下继续执行。它们用语法保护条件else {…}定义。

因此,要使用可选绑定,您可以这样做:

guard let number = anOptionalInt else {
    return
}

(请注意,在防护主体中,您必须使用control transfer statements之一才能退出当前执行代码的范围)。

如果anOptionalInt包含一个值,它将被展开并赋值给新的数字常量。保护后的代码将继续执行。如果它不包含一个值 – 防护将执行括号内的代码,这将导致控制的转移,使得紧接在之后的代码将不会被执行。

关于guard语句的真正的事情是解开的值现在可以在语句之后的代码中使用(因为我们知道未来的代码只有在可选值有值时才能执行)。这是一个伟大的消除‘pyramids of doom’通过嵌套多个if语句创建。

例如:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value,and it’s: /(number)!")

Guard还支持与if语句支持的相同的整齐的技巧,例如同时解开多个可选并使用where子句。

是否使用if或guard语句完全取决于任何未来的代码是否需要可选的包含一个值。

无合并运算符

Nil Coalescing Operator是一个漂亮的ternary conditional operator的速记版本,主要设计用于将可选项转换为非可选项。它有语法a ?? b,其中a是可选类型,b是与a相同的类型(尽管通常是非可选的)。

它本质上让你说“如果一个包含一个值,解开它。如果它不然然后返回b“。例如,你可以这样使用它:

let number = anOptionalInt ?? 0

这将定义一个Int类型的数字常量,如果它包含一个值,它将包含anOptionalInt的值,否则为0。

它只是简写:

let number = anOptionalInt != nil ? anOptionalInt! : 0

可选链接

您可以使用Optional Chaining为了调用方法或访问可选的属性。这是简单地通过在变量名后面加一个?当使用它。

例如,假设我们有一个变量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和bar是可选的,因此从qux返回的值总是可选的,而不管qux本身是否是可选的。

地图和flatMap

经常未充分利用的可选功能是使用map和flatMap函数的能力。这些允许您将非可选变换应用于可选变量。如果一个可选项有一个值,你可以应用一个给定的转换。如果它没有值,它将保持为零。

例如,假设您有一个可选字符串:

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,除了它允许您从闭包体内返回另一个可选。这意味着您可以将一个可选输入到需要非可选输入的进程中,但可以输出可选输入。

尝试!

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这种方式是尝试,捕获和处理来自throwing函数的错误的正确方法。

还有尝试?吸收误差:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure,but there's no error information available
}

但Swift的错误处理系统也提供了一种方式来“尝试”尝试!

let result = try! someThrowingFunc()

在这篇文章中解释的概念也适用于这里:如果抛出一个错误,应用程序将崩溃。

你应该只使用try!如果你可以证明它的结果永远不会在你的上下文中失败 – 这是非常罕见的。

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

资源

> Apple documentation on Swift Optionals
> When to use and when not to use implicitly unwrapped optionals
> Learn how to debug an iOS app crash

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。