理解Swift的Copy-on-Write机制

翻译:原文链接:Understanding Swift Copy-on-Write mechanisms

在Swift中,我们有引用类型(类Classes)和值类型(结构体Structs, 元组Tuples, 枚举enums)。值类型有一个Copy机制: 如果你将一个值类型变量赋值给你一个变量,或者将它作为参数传递给一个函数(inoout 参数除外), 这个值类型的数据将会被Copy。你将会有两个相同内容的值,同时被声明了两个不同的内存空间。关于引用类型和值类型的区别,请参赛Apple官方内容

既然我们要讨论Copy-on-Write机制,就有必要了解Swift对数据的存储机制。

好,我们开始吧!

什么是 Copy-on-Write?

Alt text

在Swift中,当你有比较大的数据值,且需要将它赋值或者作为一个参数传递给一个函数的时候,复制它,在性能上耗能很大,因为你需要复制它的所有数据,并重新申请一份内存空间。

为了减少这个问题,Swift标准库实现了一个特殊的机制: 当对值类型数据,例如数组,只有当它的内容改变的时候才对它进行Copy,或者当它有超过一个的引用的时候。由于此值是唯一的引用,它并不需要Copy, 可以在改变它的时候进行Copy。因为,仅是赋值或者作为一个参数传递给函数的时候,并不需要对值类型的数据进行Copy,这样既能节省内存空间,也可以提高效率。

需要重点了解的是 …

Copy-on-Write , 并不是所有值类型默认实现的。是Swift标准库中某些特定类型实现的,比如数组Array,集合Collections,等等,因为并不是Swift标准库中所有值类型有这个特性,同样的,你自己创建的值类型并没有这个特性,除非你自己去实现它。后面将会介绍如何自己实现。

让我们先看看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation

func print(address o: UnsafeRawPointer ) {
print(String(format: "%p", Int(bitPattern: o)))
}
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1

//Print with just assign
print(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
//Let's mutate array2 to see what's
array2.append(4)

print(address: array2) //0x6000000aa100

//Output
//0x600000078de0 array1 address
//0x600000078de0 array2 address before mutation
//0x6000000aa100 array2 address after mutation

以上是Copy-on-Write实现的例子。当array1被创建并且赋值给array2的时候,因为Copy-on-Write机制,两个array有相同的内存地址。当array2被改变的时候,array2才被真正的赋值并且有了新的内存地址。

为自定义值类型实现Copy-on-Write机制

你可以为自定义的值类型实现Copy-on-Write机制,下面的例子可以在Swift的源代码中找到。编写高质量的Swift代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
final class Ref<T> {
var val : T
init(_ v : T) {val = v}
}

struct Box<T> {
var ref : Ref<T>
init(_ x : T) { ref = Ref(x) }

var value: T {
get { return ref.val }
set {
if (!isUniquelyReferencedNonObjC(&ref)) {
ref = Ref(newValue)
return
}
ref.val = newValue
}
}
}
// This code was an example taken from the swift repo doc file OptimizationTips
// Link: https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst#advice-use-copy-on-write-semantics-for-large-values

上面的例子展示了如何通过引用类型为一个泛型值类型T添加Copy-on-Write机制。通过管理一个引用类型,当它的值不是为唯一的引用类型是返回一个新的实例,否则返回唯一的引用类型。

结语

Copy-on-Write是一个非常机智的方法来优化值类型的Copy,这个机制在Swift中被广泛应用,即使大多数的时候我们并没有直观的看到它,底层函数帮我们实现了它。但是知道它的实现原理对我们日常的编程也是非常有好处的。

That's all folks

以上是Copy-on-Write的所有内容,希望你喜欢。