【问题标题】:Optional array vs. empty array in SwiftSwift 中的可选数组与空数组
【发布时间】:2015-01-04 20:48:42
【问题描述】:

我在 Swift 中有一个简单的 Person 类,看起来像这样:

class Person {
    var name = "John Doe"
    var age = 18
    var children = [Person]?

    \\ init function goes here, but does not initialize children array
}

不用将children 声明为可选数组,我可以简单地声明它并将其初始化为一个空数组,如下所示:

var children = [Person]()

我正在尝试决定哪种方法更好。将数组声明为可选数组意味着它根本不会占用任何内存,而空数组至少分配了一些内存,对吗?所以使用可选数组意味着至少会节省一些内存。我想我的第一个问题是:这里真的有任何实际的内存节省,还是我对此的假设不正确?

另一方面,如果它是可选的,那么每次我尝试使用它时,我都必须在添加或删除对象之前检查它是否为nil。所以会有一些效率损失(但我想不会很多)。

我有点喜欢可选的方法。不是每个Person 都会有孩子,所以为什么不让children 成为nil,直到Person 决定安定下来养家糊口?

无论如何,我想知道一种方法是否有其他特定的优点或缺点。这是一个反复出现的设计问题。

【问题讨论】:

  • 你有一个有效的问题,但我认为赞成和反对的论点是危险的——基于过早的优化而不是语义。关注代码的“可供性”要好得多——它在多大程度上暗示了意图(无论上下文可能是什么)。 Swift 有很多我不喜欢的地方,但对我来说, Optional 类型是一个真正的宝石。虽然您的问题没有“正确”的答案,但我在像您这样的情况下广泛使用它。在可选子项的情况下使用可选项似乎完全合理且极简。
  • 我会假设数组中的项目数为零意味着有零个孩子。一个 nil 数组表明存在多个不能为零且不能为非零的子级。返回 nil 没有逻辑,因为它没有任何意义。由于必须维护 20 年前编写的代码,我可以告诉您,如果您通过返回 nil 来暗示其他含义,那么有些人会对此感到困惑,并想知道您打算如何处理空数组没有的 nil完成。空数组的性能可能并不重要。

标签: arrays swift optional


【解决方案1】:

Swift 旨在利用可选值和可选展开。

可以也将数组声明为 nil,因为它会为您节省非常少(几乎不明显)的内存量。

我会选择一个可选数组,而不是一个代表 nil 值的数组,以保持 Swift 的设计模式快乐 :)

我也觉得

if let children = children {

}

看起来比:

if(children != nil){

}

【讨论】:

    【解决方案2】:

    我将与 Yordi 做相反的情况 - 一个空数组就像清楚地表明“这个人没有孩子”,并且会为您省去很多麻烦。 children.isEmpty 可以轻松检查孩子的存在,您无需打开包装或担心意外的nil

    另外,请注意,将某些内容声明为可选并不意味着它占用零空间 - 这是 Optional<Array<Person>>.None 情况。

    【讨论】:

    • 完全同意。使其可选也强制进行两次检查,如果我们真的想知道是否有超过 1 个孩子(打开包装然后检查它是否不为空),这会使代码稍微复杂化,但没有真正的好处。在我看来,空数组很清楚。
    • 我想知道性能问题,如果我有一个沉重的类,与.None相比,这种类型的数组会是一个很大的开销吗?或者 Swift 中是否有任何优化不为最初的空数组分配空间?
    【解决方案3】:

    在空数组或可选数组之间进行选择的能力使我们能够应用从语义角度更好地描述数据的数组。

    我会选择:

    • 如果列表可以为空,则为空数组,但这是一种暂时状态,最终它应该至少有一个元素。非可选表明数组不应该为空
    • 如果列表可能在容器实体的整个生命周期内为空,则为可选。作为一个可选项清楚地表明该数组可以为空

    让我举几个例子:

    • 包含主数据和详细信息的采购订单(每个产品一个详细信息):采购订单可以有 0 个详细信息,但这是一种暂时状态,因为包含 0 个产品的采购订单没有意义
    • 有孩子的人:一个人可能一辈子都没有孩子。这不是一个暂时的状态(虽然也不是永久的),但是使用一个可选的很明显,一个人没有孩子是合法的。

    请注意,我的意见只是让代码更加清晰和不言自明 - 我认为选择一个选项在性能、内存使用等方面没有任何显着差异。

    【讨论】:

    • 数组中的项数为子项数。零项目是零孩子。对数组使用 nil 意味着计数既不为零也不非零,这几乎是无稽之谈。 nil 可能有意义的唯一情况是,如果您想将无法计算的数字(未知)传递给调用者,这是唯一可能的其他状态。如果数字是已知的,则返回孩子的数量,并通过在数组中有那么多项目来返回它。
    • “如果列表可能在容器实体的整个生命周期内为空,则为可选”这是我永远不会想到的,因此使用 nil 并不直观,需要注释。事实上,任何使用 nil 都需要一些代码 cmets,这让我觉得最好不使用它并返回一个空数组,这非常清楚,不需要代码 cmets 来理解。
    【解决方案4】:

    有趣的是,我们最近很少就工作中的同一个问题进行讨论。

    有些人认为存在细微的语义差异。例如。 nil 表示一个人没有孩子,但0 是什么意思?这是否意味着“有孩子,全部为 0”?就像我说的,纯语义 “有 0 个孩子”“没有孩子” 在代码中使用这个模型时没有区别。在那种情况下,为什么不选择更直接、更少防卫的方法呢?

    有些人建议保留nil 可能表明,例如,当从后端获取模型时出现问题,我们得到错误而不是孩子。但我认为模型不应该尝试拥有这种类型的语义,并且nil不应该被用作过去某些错误的指示。

    我个人认为模型应该尽可能的笨,在这种情况下dumbest选项是空数组。

    拥有一个可选项将使您将? 拖到天结束,并一遍又一遍地使用guard letif let??

    NSCoding 实现必须有额外的解包逻辑,当您在任何视图控制器中显示该模型时,您必须执行 person.children?.count ?? 0 而不是直接的 person.children.count

    所有这些操作的最终目标是在 UI 上显示一些东西。 你真的会说

    “这个人没有孩子”和“这个人有0个孩子”对应nil和空数组?我希望你不会:)

    最后一根稻草

    最后,这真的是我最有力的论据

    在 Cocoa 框架中有大量这样的例子:UIViewController::childViewControllers 等等。

    即使来自纯 Swift 世界:Dictionary::keys 虽然这可能有点牵强。

    为什么一个人可以有nil 孩子,但不能有SKNode?对我来说,这个比喻是完美的。嘿,连SKNode 的方法名都是children :)

    我的观点:必须有一个明显的理由将这些数组保留为可选项,就像一个非常好的数组一样,否则空数组提供相同的语义而展开更少。

    最后一根稻草

    最后,一些非常好的文章的参考,每一篇

    在 Natasha 的帖子中,您会找到 link to NSHipster's blog post,在 Swiftification 段落中,您可以阅读以下内容:

    例如,许多 API 已被修改为返回一个空数组,而不是将 NSArray 返回值标记为可空值 — 从语义上讲,它们具有相同的值(即,没有),但使用非可选数组要简单得多

    【讨论】:

    • "nil" 对我来说是数据库中的空值(与 java 和 c# 的空值相同)。这意味着我们不知道这个人是否有孩子。值为 0 表示我们知道此人没有孩子。
    • @chrisl08,这是一个很好的例子,nil 在问题域中是有意义的。我仍然认为,如果不需要模拟“我们不知道”或类似状态,那么就没有必要使用nil。例如。在银行应用程序中,我们(银行)肯定知道客户有帐户,因此 nil 和空数组将具有相同的语义。与SKNode 相同,SpriteKit 框架确定节点是否有子节点,因此它们将其建模为空数组。
    • 完全同意,这就是为什么他们让我们可以选择在 rdbms 系统中将列标记为可空,以及为什么在 swift/java/c# 等编程语言中必须有可空变量
    【解决方案5】:

    有时候不存在的东西和空的东西是有区别的。

    假设我们有一个应用程序,用户可以在其中修改电话号码列表,我们将上述修改保存为modifiedPhoneNumberList。如果未发生任何修改,则数组应为 nil。如果用户通过删除它们修改了解析的数字,则所有数组都应该为空。

    Empty 表示我们将删除所有现有的电话号码,nil 表示我们保留所有现有的电话号码。区别在这里很重要。

    当我们无法区分属性是空的还是不存在的,或者空无所谓的时候,我们可以选择。如果Person 失去了他们唯一的孩子,我们只需要删除那个孩子并拥有一个空数组,而不是检查count 是否为1,然后将整个数组设置为nil

    【讨论】:

    • 这个。拥有选项还可以让您(在一定程度上)了解变量的历史。就像 Deco 说的那样,如果你有一个可选的 children 数组,它在某些时候是空的,这意味着 Person 有孩子,但在某个时候不知何故失去了他们。在 UI 术语中,您可以区分“Person never have children”和“Person没有任何孩子”
    【解决方案6】:

    我总是使用空数组。

    在我看来,Swift 中可选的最重要目的是安全地包装一些可能为 nil 的值。一个数组已经充当了这种类型的包装器——你可以询问数组里面是否有任何东西,并使用 for 循环、映射等安全地访问它的值。我们需要在包装器中放置一个包装器吗?我不这么认为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多