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

时间:2014-12-23 15:00:44

标签: ios macos swift optional

选项的全部原因是为了防止因命中分配给nil / null / none的变量而导致的运行时崩溃。因此,变量不能为零;相反,它们可以包含在一个可选类型中,表示它们为Some或None,并解包以获取Some的具体内容,或者为nil。

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

所以:在我看来,要做的是避免选项,除非必要时(例如,在调用返回它们的框架方法时)。但是,如果我不使用选项,那意味着我的对象引用必须是非零的,我无法弄清楚如何处理由于某种原因不应该出现的情况。是,或者不是分配给对象引用的值。

我的问题是:我如何避免需要零?看起来它需要一种不同的编程方法。 (或者我应该只使用选项,如果这就是我正在做的事情,那么除了像其他语言那样简单地对对象引用进行空分配外,它有什么用呢?)

我知道这可能是一个主观问题,但我还应该问其他问题呢?我并没有试图哄骗或激起辩论,我真的很想知道在编写更多Swift代码时正确的方法是什么。

4 个答案:

答案 0 :(得分:83)

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

例如,选择Array.first成员。这是一个方便的实用程序,它为您提供数组的第一个元素。当您可以致电a.first时,为什么能够拨打a[0]是有用的?因为在运行时,数组可能为空,在这种情况下a[0]将会爆炸。当然,您可以事先检查a.count - 但是再次检查

  

一个。你可能会忘记,

     

     

湾这会导致相当丑陋的代码。

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!。但是!对你来说就像是一个信号 - 当心,你正在做一些危险的事情,如果你的代码不完全正确,你会感到抱歉。

选项也有助于使替代品略微漂亮。假设您想要在数组为空时默认值。而不是:

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

你可以这样写:

array.first ?? somedefault

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

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

find返回一个可选项时,它很自然,当你理解了可选的习惯用法时,要意识到它的原因是因为结果可能因为显而易见的原因而无效。上面提到的所有关于安全性的事情也同样适用 - 你不能不小心忘记,并将结果用作索引,因为你必须先解开它。

那就是说,你可以过度使用选项,因为它们是必须检查的负担。这就是为什么数组下标不会返回选项 - 为了不断检查和解包它们会产生很多麻烦,特别是当你知道你使用的索引是有效的时候(例如,你在一个有效的数组索引范围内的for循环中,人们会经常使用!,从而使代码混乱而没有任何好处。但是,添加了第一个和最后一个辅助方法,以涵盖人们想要快速执行操作而不必先检查数组大小但希望安全地执行操作的常见情况。

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

更好的是,如果可以完全避免失败的可能性。例如,当filter未匹配任何元素时,它不会返回nil可选项。它返回一个空数组。你可能会说,“显然它会”。但是你会惊讶地发现,当你真正应该返回一个空数时,你会看到有人像一个数组一样选择返回值。所以你完全正确地说你应该避免选择,除非它们是必要的 - 这只是一个必要方法的问题。在上面的例子中,我会说它们是必要的,并且是替代方案的更好解决方案。

答案 1 :(得分:13)

  

或者我应该只使用选项,如果这是我正在做的事情,   如何简单地对对象进行空赋值更好   像其他语言一样的参考文献?

如果你的计算可能需要返回一个“特殊”值,那么是的,在Swift中你应该使用选项。它们优于可空类型,因为它们是显式。很容易错过一个指针可能是nil的情况,它更难(但完全可能)搞砸了选项。

  

如果你使用“if let”来安全地打开它们,你可以避免崩溃,但是你   被困在“if let”语句的范围内与之一起工作   价值,你仍然需要处理潜在的零案件。

这是一个功能。我的意思是,这是可选类型的全部要点:你必须处理这两种情况(nil和非 - nil),你必须明确它。

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

答案 2 :(得分:1)

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??等来表示如何处理或传递零值。

答案 3 :(得分:1)

这样考虑:在需要可选变量public static DataTable ToDataTable<T>(List<T> items) { DataTable dataTable = new DataTable(typeof(T).Name); PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo prop in Props) { var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType); dataTable.Columns.Add(prop.Name, type); } foreach (T item in items) { var values = new object[Props.Length]; for (int i = 0; i < Props.Length; i++) { values[i] = Props[i].GetValue(item, null); } dataTable.Rows.Add(values); } return dataTable; } 的任何情况下,都可以等效地使用一对非可选变量:

var value: Double?

每当您想使用var value: Double var valueExists: Bool 时,都需要手动检查是否value

valueExists

使用为处理可选内容而设计的语言功能通常比传递双变量并手动执行这些检查容易得多。让编译器始终提醒您处理if valueExists { /* do something */ } else { /* handle case where value is not valid */ } 无效的情况也更安全。出于这些原因,发明了可选技术!