在Swift中断言以及为什么要使用它们

断言:自信而有力地陈述事实或信念

您是否曾经编写代码并将其充满print("Something happened here.")胡说八道? 您知道,从理论上讲,它不应该被调用,但是在确实发生这种偶然性的情况下,您想知道吗? 现在,您在代码中散布了随机行,这些行并没有给您的应用程序带来任何实际好处。 什么时候发生—令人震惊—它确实发生了,但是调试器充满了垃圾邮件,您错过了它。

我第一次尝试在代码中使用asserts是在今年年初,当时我正在研究一些测试用例。 在我刚开始进行测试时,我开始发现到处都是这些assert(value != nil, "Error: ...")窍门。 我很快发现断言对于您的项目是非常有用的测试和调试工具。

但是他们怎么办? 断言要求给定条件为真,否则它将立即使应用程序崩溃。 但这不是很糟糕吗? 在生产中,是的! 通常,为了用户的利益,您通常希望避免崩溃,但是有时应用程序处于严重的状况,以至于无法继续进行危险。 如果您正在调试应用程序,那么崩溃可能非常有用,因为它可能直接导致您遇到问题。

当我从Web上的JSON文件中获取数据时,我可能不会总是收到期望的结果。 这可能是因为像打字错误一样简单,例如尝试访问data["result s "]而不是data["result"] 。 例如,如果我要查询小狗社交媒体API,则可能期望与特定的小狗匹配。

但是,如果那只幼犬的帐户由于过于可爱而被撤消怎么办? 这是一种现实情况,人们似乎并没有认真对待。 无论如何,如果我的代码依靠某个名为“ Belle”的小狗存在于数据库中,而她却没有,那么我的应用程序可能会以奇怪的方式响应。

 让myPuppyInfo:[String:Any] = ... 
 守卫让puppyData = myPuppyInfo [“ puppies”]为?  [String:任何], 
让Alice = puppyData [“ Alice”]表示为? [String:任何],
让Belle = puppyData [“ Belle”]为? [String:任何]其他{
assertionFailure(“解析JSON时出现问题”)
返回Result.failure(.unableToParse)
}

在上面的示例中,我希望获得两只狗的数据:爱丽丝和百丽。 只是,出于某种原因,Belle的帐户未显示在JSON中? 借助JSON逻辑中的快速断言,我可以确保在公开发布之前引起我的注意,这使我有时间确保我的代码正确解决了该问题。

但是,如果程序仍然要崩溃,为什么要在断言之后包含一个返回值? 虽然在调试时永远不会调用return,但是断言不会被拉入生产代码!

如果我正在Xcode中调试它,则将调用断言,程序将结束,但是,如果此代码在App Store中,则将跳过断言,并且应用程序应能够适当地响应。

在调试中,断言将导致程序停止运行,并将您的注意力转移到出现问题的确切位置,这也是该特定断言所在的位置。 最好的部分是,当应用程序停止运行时,编译器仍在营业。 这意味着您仍然可以访问变量及其内容来查看问题所在。

断言有三种主要风格:

  1. 断言
  2. 前提条件
  3. 致命错误

每一个都用于在可调试状态下停止程序的执行,就像您在第256行的开头设置了断点一样。如果断言中包含一条消息,那么它将在调试之前立即传递给调试器。停下来

尽管断言,前提条件和致命错误产生了相似的结果,但它们的主要区别在于它们何时处于活动状态。

我们继续进行之前的旁注:Xcode将使用以下三个优化标志之一来编译您的构建:

  1. Onone无需优化即可编译
  2. O :优化编译
  3. Ounchecked进行优化编译并删除运行时安全检查,该检查用于未经检查的发行版

在编写和测试代码时使用Onone-O或发行版仅用于表示App Store的发行版。 -Ounchecked是一个发行版本,剥离了Swift 赖以生存的运行时安全检查。 我不建议您使用未经检查的内部版本,除非您真的知道自己在做什么,因为它可能导致意外的危险结果,而您的应用通常会崩溃。

回到断言!

断言将仅在Xcode的Debugging(-Onone)配置中编译,而前提条件将在Debugging(-Onone)和Release(-O)条件中编译。 那么这对你的意义是什么?

断言仅在您编写,测试和调试代码时运行! 前提条件没有那么挑剔,可以在最终应用商店中使用,也可以在最终版本中运行。

那么我们何时以及如何使用它们呢?

断言

当您想验证代码时,应该使用断言,但是其中的问题并不一定会破坏应用程序。 例如,当您使用可选的初始化程序且问题设置为仅返回nil时。 通常,返回nil是响应错误的适当方法,但是从技术上讲,它仍然是解决实际问题的临时工具。 用苹果的话来说:

“将此功能用于在测试过程中处于活动状态的内部完整性检查,但不会影响运输代码的性能。” — Apple文档

我发现断言的一个很好的用途是当我通常会打印出我不期望的错误时将其放入。 Guard语句是我在代码中最喜欢的一些位置,以利用断言。

Swift有两个主要的assert函数: assert(_ condition: Bool, _ message: String)assertionFailure(_ message: String) 。 那是实际功能的精简版,但是对于我们来说,只知道message是一个可选参数,默认为空字符串。

要使用assert(...)您将需要包含要测试的条件,例如true5 < 10var == "String"

它们的实际用法如下所示:

 让pupName:字符串?  =“爱丽丝” 
  //仅在pupName!= nil时继续 
assert(pupName!= nil,“ pupName变量为nil”)
断言(!pupName.isEmpty)
// **成功:继续执行...
  //仅在pupName ==“ Belle”时继续 
如果(pupName!=“ Belle”){
assertionFailure(“我以为这是美女?”)
}
// **失败:结束执行(但仅当我们为调试而编译时)

有趣的事实:Swift的编译器在为发行版进行编译时会完全忽略断言,就像它会忽略注释一样。

前提条件

当您在继续执行代码之前甚至在生产中需要满足必要条件时,应使用前提条件。 例如,如果Swift检测到越界数组,它将迫使应用程序崩溃。 用苹果的话来说:

“使用此功能可以检测到即使在运输代码中也必须阻止程序继续运行的情况。” — Apple文档

前置条件可以两种方式之一使用,它们的使用与断言几乎相同。

 让pupName:字符串?  =“美女” 
  //仅在pupName!= nil时继续 
前提条件(pupName!= nil,“ pupName变量为nil”)
前提条件(!pupName.isEmpty)
// **成功:继续执行...
  //仅在pupName ==“ Belle”时继续 
如果(pupName!=“ Belle”){
preconditionFailure(“我以为这是美女?”)
}
// **失败:结束执行(如果我们被编译用于调试发布)

致命错误

我们上面提到的断言的最后一种fatalErrorfatalError 。 致命错误本质上是preconditionFailures ,除了一个区别。 当像我们前面讨论的那样为非检查发布(-Ounchecked)编译项目时,它将假定不会调用assertionFailurepreconditionFailure

使用致命错误非常简单。 就像preconditionFailure ,要添加的唯一有用的参数是一条消息,但不是必需的。 推送到控制台的消息的默认值是一个空字符串。

一些开发人员可能选择阻止越狱电话的用户使用其应用程序,以防止游戏中的作弊行为或其他安全问题。 尽管如今越狱的情况越来越少,但仍有一些较旧的iOS版本可能会受到攻击。 如果您可以检测到用户的设备已受到威胁,则您的应用可能的解决方案可能是:

 让isJailbroken:布尔= ... 
 如果(isJailbroken){ 
致命错误()
}

致命错误是退出程序执行的快速简便的方法,无论编译器的内部版本优化如何。 尽管如今检查不容易受到攻击的设备已不那么重要,但致命错误仍然有许多其他用途。

当程序被要求除以零时,它也可能会发送致命错误。

例如,假设我们正在开发银行应用程序。 为了防止数据泄露,我们进行了最后的努力,我们希望在向用户显示敏感数据之前验证他们是否已登录。 如果由于某种奇怪的原因而没有检测到错误,则可以记录该尝试,将其发送到相关的网络资源,然后执行致命错误以希望清除应用程序中存在的任何错误。