在 Swift 中,我如何避免选项和 nil 对象引用?

Posted

技术标签:

【中文标题】在 Swift 中,我如何避免选项和 nil 对象引用?【英文标题】:In Swift, how do I avoid both optionals and nil object references? 【发布时间】:2014-12-23 15:00:43 【问题描述】:

使用可选项的全部原因是为了防止由于命中分配给 nil/null/none 的变量而导致运行时崩溃。因此,变量不能为零;相反,它们可以包装在一个可选类型中,将它们表示为 Some 或 None,并展开以获得 Some 或 nil 的特定内容。

但是,如果您使用 ! 或使用隐式展开的 Optionals 将它们全部展开,您只是引入了运行时崩溃的可能性,因为您是一个不完美的编码器。如果你使用if let 安全地解包它们,你可以避免崩溃,但是你被困在if let 语句的范围内以使用 Some 中的值,你仍然必须处理潜在的 nil 情况。如果你使用 ?为了调用一个方法而临时解包,完成后会被重新包装,引入了多层可选包装的混乱可能性。

所以:在我看来,除了必要时(例如,调用返回它们的框架方法时),要做的事情就是避免使用可选项。但是,如果我不使用可选项,这意味着我的对象引用必须是非零的,而且我不知道如何处理由于某种原因不应该存在或不存在的情况,分配给对象引用的值。

我的问题是:如何避免需要 nil?似乎它需要不同的编程方法。 (或者我应该只使用可选项,如果这就是我正在做的事情,它有什么比像其他语言那样简单地对对象引用进行空分配更好?)

我知道这可能是一个主观问题,但我还能在哪里问呢?我不是想挑衅或挑起争论,我真的很想知道在我编写更多 Swift 代码时正确的方法是什么。

【问题讨论】:

听说过面向铁路的编程吗?这是一篇在 f# fsharpforfunandprofit.com/posts/recipe-part2 中解释该概念的帖子 好问题,看看函数式编程和(可能)函数式 Swift 如果您因为过于主观而即将投票结束这个问题,您能否评论一下它有什么具体问题?这个问题真的是我怎么做的,而答案确实回答了它。如果您真的认为应该关闭它,我可以做些什么来改进它? 【参考方案1】:

你是对的,可选可能会很痛苦,这就是为什么你不应该过度使用它们。但它们不仅仅是您在使用框架时必须处理的事情。它们是一个非常常见问题的解决方案:如何处理返回结果可能正常或不正常的调用。

例如,以Array.first 成员为例。这是一个方便的实用程序,可为您提供数组的第一个元素。为什么可以打电话给a.first,而你可以打电话给a[0]?因为在运行时数组可能是空的,在这种情况下a[0] 会爆炸。当然你可以事先检查a.count——然后再检查一次

一个。你可能会忘记,

b.这会导致代码非常丑陋。

Array.first 通过返回一个可选项来处理这个问题。因此,您必须先解开可选项,然后才能使用数组的第一个元素。

现在,关于仅存在于带有if let 的块内的未包装值的问题。想象一下并行代码,检查数组计数。应该是一样的吧?

if a.count > 0 
    // use a[0]

// outside the block, no guarantee
// a[0] is valid

if let firstElement = a.first 
    // use firstElement

// outside the block, you _can't_
// use firstElement

当然,如果计数为零,您可以执行诸如提前从函数返回之类的操作。这可行,但有点容易出错——如果你忘了这样做,或者把它放在一个没有运行的条件语句中怎么办?基本上你可以对array.first 做同样的事情:在函数的早期检查计数,然后再做array.first!。但是! 对你来说就像一个信号——当心,你正在做一些危险的事情,如果你的代码不完全正确,你会很抱歉。

Optional 还有助于使替代方案更漂亮。假设如果数组为空,您想默认值。而不是这个:

array.count > 0 ? a[0] : somedefault

你可以这样写:

array.first ?? somedefault

这在几个方面更好。它将重要的事情放在前面:您想要的值是表达式的开始方式,然后是默认值。与三元表达式不同,它首先使用检查表达式,然后是您实际想要的值,最后是默认值。它也更加万无一失——更容易避免打错字,而且该错字也不可能导致运行时爆炸。

再举一个例子:find 函数。这将检查一个值是否在集合中并返回其位置的索引。但该值可能不存在于集合中。其他语言可能会通过返回结束索引(它不指向一个值,而是指向最后一个值)来处理这个问题。这就是所谓的“哨兵”值——一个看起来像常规结果但实际上具有特殊含义的值。与前面的示例一样,您必须在使用之前检查结果是否不等于结束索引。但是你必须知道才能做到这一点。您必须查找 find 的文档并确认它是如何工作的。

find 返回一个可选项很自然,当您理解可选项的习惯用法时,就会意识到它这样做的原因是因为结果可能由于明显的原因而无效。上面提到的关于安全的所有相同的事情也适用——你不能不小心忘记,并将结果用作索引,因为你必须先解包它。

也就是说,您可以过度使用可选项,因为它们是必须检查的负担。这就是数组下标不返回可选项的原因——它会带来很多麻烦,必须不断检查和解包它们,尤其是当你知道你正在使用的索引是有效的事实时(例如,你在在数组的有效索引范围上的 for 循环)人们将不断使用!,从而使他们的代码混乱而无益。但是随后添加了 first 和 last 之类的辅助方法,以涵盖人们确实希望快速执行操作而不必先检查数组大小但又希望安全地执行操作的常见情况。

(另一方面,Swift 字典预计会通过无效的下标定期访问,这就是为什么他们的 [key] 方法确实返回一个可选的)

如果可以完全避免失败的可能性,那就更好了。例如,当 filter 不匹配任何元素时,它不会返回 nil 可选。它返回一个空数组。 “显然它会”,你可能会说。但是你会惊讶地发现,当实际上他们应该返回一个空值时,你会经常看到有人将返回值作为一个可选的数组。所以你说你应该避免可选选项是完全正确的,除非它们是必要的——这只是必要意味着什么的问题。在上面的示例中,我会说它们是必要的,并且是替代方案的更好解决方案。

【讨论】:

这是一个非常周到、深入、准确的答案,我感谢您抽出时间用实际用例来说明何时或何时没有可选选项有意义。谢谢! 很棒的东西。你可能应该写一个快速的博客或其他东西;) 我实际上编写了一个自定义过滤器函数,它返回可选数组。你在那个问题上打败了我。 :D【参考方案2】:

或者我应该只使用可选项,如果这就是我正在做的, 它有什么比简单地对对象进行空分配更好的呢? 像其他语言一样的引用?

如果您的计算可能需要返回一个“特殊”值,那么是的,在 Swift 中您应该使用可选项。它们比可空类型更好,因为它们是显式的。很容易漏掉一个指针可能是nil 的情况,而用可选项搞砸要困难得多(但完全有可能)。

如果你使用“if let”来安全地打开它们,你可以避免崩溃,但是你 被困在“if let”语句的范围内以使用 值,你仍然需要处理潜在的 nil 情况。

这是一个特点。我的意思是,这就是可选类型的全部意义所在:您必须同时处理两种情况(nil 和非nil),并且必须明确说明。

有关类似概念的示例,请参阅 Haskell 的 Maybe type and monad。 Maybe 类型完全等同于可选类型,Maybe monad 使得使用这些可选值“链接”操作变得非常容易,而无需一直手动检查空值。

【讨论】:

这有助于区分可选项是显式的还是空引用不是这样的。谢谢。 你能评论一句“如果你使用?来临时解包来调用一个方法,它会在完成后重新包装,引入了多层可选包装的混乱可能性。”? swift 选项不是单子的,而是像函子一样表现吗? @Bergi,在 Objective-C 中,您可以向 nil 对象发送消息,并且不会发生任何不好的事情。 Swift 中的可选链接只是这个特性的一个通用版本。 AFAIK 你不能将它用于比嵌套属性访问、链式方法调用等更复杂的事情。 (不确定这是否回答了你关于 monad 和 functors 的问题,不幸的是我对函数式编程了解不多。) 哦,我以为你知道 Maybe monad。 OP 似乎害怕像Optional<Optional<Result>> 这样的类型,当他将一个返回可选结果的方法链接到可选时,他会得到这种类型。这可以(自动)解包到Optional<Result>吗? 在 Swift 中,如果你使用 ? 链接多个可选访问,你只会得到一个“单级”可选的返回,比如 Maybe Int 如果最右边的调用返回和 Int。我知道 Maybe monad 允许您链接多个使用 Maybe 类型的表达式,而不必一直手动检查返回值,所以我知道这两个功能相似,并且 Haskell 解决方案更通用,适用于其他情况。我不知道更多。【参考方案3】:

Swift 2 及更高版本还包括guard let,它解决了这个问题中提到的许多问题。如果需要,您甚至可以重复使用变量名称:

func compute(value: Double?) -> Double? 
    guard let value = value  else  return nil 

    // ... `value` is now a regular Double within this function

当第一部分返回nil时,您还可以使用可选链接 (?) 来短路表达式:

let answer = compute(value: 5.5)?.rounded()

nil 合并 (??) 提供一个默认值而不是 nil:

let answer:Double = compute(value: 5.5) ?? 0

我同意其他帖子的观点,即选项并不是解决所有问题的正确工具,但您无需避免它们。使用if letguard let?? 等来表示如何处理或传递 nil 值。

【讨论】:

【参考方案4】:

这样想:在任何需要可选变量var value: Double?的情况下,您都可以等效地使用一对非可选变量:

var value: Double
var valueExists: Bool

当你想使用value时,你需要手动检查valueExists

if valueExists  /* do something */ 
else  /* handle case where value is not valid */ 

使用为处理选项而设计的语言特性通常比传递对偶变量并手动进行这些检查要容易得多。让编译器始终提醒您处理value 无效的情况也更安全。正是出于这些原因,才发明了可选项!

【讨论】:

以上是关于在 Swift 中,我如何避免选项和 nil 对象引用?的主要内容,如果未能解决你的问题,请参考以下文章

!= nil 语句中的 Swift 选项[重复]

完成块在 Swift 3 中变为 nil

swift 可选项(Optional)

在 Swift 3 中使用 prepareForSegue 分配对象变为 nil

Swift 4:将 Bool 选项打印为未包装的 true 或 false 或包装的 nil?

对象在 swift 中是 nil segue