编写高性能 Swift - 有效使用容器类型的三点建议

380次阅读  |  发布于3年以前

Swift 标准库提供的一个重要功能是通用容器 Array 和 Dictionary。本文将说明如何高效地使用这些类型。

建议:在数组中使用值类型

在 Swift 中,类型可以分为两个不同的类别:值类型(结构,枚举,元组)和引用类型(类)。它们的一个关键区别是,值类型不能包含在 NSArray 中。因此,当使用值类型时,优化器可以消除 Array 中大部分的开销,这些开销对于处理将数组支持 NSArray 的可能性是必需的。

此外,与引用类型相反,值类型仅在递归包含引用类型时才需要引用计数。通过使用没有引用类型的值类型,可以避免额外的保留,释放 Array 内部的空间。

// Don't use a class here.
struct PhonebookEntry {
  var name: String
  var number: [Int]
}

var a: [PhonebookEntry]

请记住,在使用大的值类型和使用引用类型之间需要权衡。在某些情况下,复制和移动大的值类型的开销将超过消除桥接和保留/释放开销的成本。

建议:当不需要 NSArray 桥接时,将 ContiguousArray 与引用类型一起使用

如果您需要引用类型的数组,并且该数组不需要桥接到 NSArray 时,请使用 ContiguousArray 而不是 Array:

class C { ... }
var a: ContiguousArray<C> = [C(...), C(...), ..., C(...)]

建议:使用适当的突变代替对象重新分配

Swift 中的所有标准库容器都是值类型,使用 COW(写时复制)来执行拷贝,而不是显式拷贝。在许多情况下,这允许编译器通过保留容器而不是执行深拷贝来排除不必要的拷贝。仅当容器的引用计数大于 1 并且容器发生突变时,才通过复制基础容器来完成此操作。例如,在下面的示例中,将 c 分配给 d 时不会发生拷贝,但是当 d 通过添加 2 进行结构突变时,将拷贝 d,然后将 2 添加到 d

var c: [Int] = [ ... ]
var d = c        // No copy will occur here.
d.append(2)      // A copy *does* occur here.

如果用户不小心,有时 COW 可能会引入其他意外副本。这方面的一个示例是尝试通过函数中的对象重新分配执行变异。在 Swift 中,所有参数都以 +1 传递,即参数保留在调用点之前,然后在被调用者的末尾释放。这意味着如果编写如下函数:

func append_one(_ a: [Int]) -> [Int] {
  var a = a
  a.append(1)
  return a
}

var a = [1, 2, 3]
a = append_one(a)

由于赋值的原因,a 可能是一个拷贝,尽管 a 在 append_one 之后没有使用。可以通过使用 inout 参数来避免这种情况:

func append_one_in_place(a: inout [Int]) {
  a.append(1)
}

var a = [1, 2, 3]
append_one_in_place(&a)

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8