Swift 中的 struct
, enum
, tuple
是值类型,class
是引用类型。
值类型在传递的时候是直接拷贝一份数据副本,而引用类型不拷贝数据,只是增加引用计数。
为了避免内存浪费,Swift 对值类型增加了一个写时复制(Copy-On-Write)的特性,只有在赋值后做了修改才会发生拷贝数据副本的行为,否则就和引用类型一样共享一份数据。
当然写时复制这个特性并不是免费的,也不是所有值类型都拥有这个特性。
struct Child {
let name: String
private(set) var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
mutating func growup() {
age = age + 1
}
}
定义一个 Array
存储数据:
var my_children = [
Child(name: "aaa", age: 11),
Child(name: "bbb", age: 12),
Child(name: "ccc", age: 13),
Child(name: "ddd", age: 14),
Child(name: "eee", age: 15)
]
对数据进行修改:
var child = my_children[3]
child.growup()
let child2 = my_children[3]
因为是值类型,所以 var child
是把数据拷贝了一份,所做的修改都不会影响数组中的数据,所以再用 let child2
取出数据会发现互不影响。
要修改数组里的内容,有两种方法:
把修改后的数据替换数组元素
var child = my_children[3] child.growup() my_children[3] = child
使用
inout
直接拿到数组元素进行修改func growup(child: inout Child) { child.growup() } growup(child: &my_children[3])
inout
只能用于函数的参数类型中,所以需要定义一个方法。
实现写时复制
如果 struct
里都是值类型,那么发生拷贝的时候,都会生成新的副本。
如果 struct
里有引用类型,比如某些字段类型是 class
,那么在发生拷贝的时候,这些引用类型不会生成新的副本。
如果需要生成全新的拷贝副本,就需要自己实现写时复制
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 !isKnownUniquelyReferenced(&ref) {
ref = Ref(newValue)
return
}
ref.val = newValue
}
}
}
以上代码来自官方的例子: Advice: Use copy-on-write semantics for large values
实现写时复制关键是使用 isKnownUniquelyReferenced
判断对象的是否有多个持有者,如果有多个持有者就在发生修改的时候生成一个新的拷贝,否则就在原对象上直接做修改。