什么是Copy-On-Write(寫時(shí)復(fù)制)久妆?
我們將一個(gè)值類型分配給另一個(gè)值類型時(shí)晌杰,我們都有原始對(duì)象的副本:
let myString = "Hello"
var myString2 = myString // myString is copied to myString2
myString2.append(" World!")
print("\(myString) - \(myString2)") // prints "Hello - Hello World!"
如果我們只復(fù)制一個(gè)普通的字符串,我們可能不會(huì)有性能問(wèn)題筷弦。
當(dāng)我們擁有包含數(shù)千個(gè)元素的數(shù)組時(shí)肋演,我們可能會(huì)開(kāi)始遇到一些麻煩,并且我們將它們復(fù)制到我們的應(yīng)用程序中烂琴。為此惋啃,Array 有一種不同的復(fù)制方式,稱為寫時(shí)復(fù)制监右。
當(dāng)我們將一個(gè)數(shù)組分配給另一個(gè)數(shù)組時(shí)边灭,我們沒(méi)有副本。這兩個(gè)數(shù)組共享同一個(gè)實(shí)例健盒。這樣绒瘦,我們就沒(méi)有一個(gè)大數(shù)組的兩個(gè)不同副本,我們可以使用同一個(gè)實(shí)例扣癣,具有更好的性能惰帽。然后,當(dāng)兩個(gè)數(shù)組之一發(fā)生變化時(shí)父虑,我們就有了一個(gè)副本该酗。
舉個(gè)栗子:
var array1 = [1, 2, 3, 4]
address(of: array1) // 0x60000006e420
var array2 = array1
address(of: array2) // 0x60000006e420
array1.append(2)
address(of: array1) // 0x6080000a88a0
address(of: array2) // 0x60000006e420
func address(of object: UnsafeRawPointer) -> String {
let addr = Int(bitPattern: object)
return String(format: "%p", addr)
}
我們可以注意到,在這個(gè)例子中士嚎,兩個(gè)數(shù)組共享相同的地址呜魄,直到其中一個(gè)發(fā)生變化。這樣莱衩,我們可以array1多次分配給其他變量爵嗅,而不必每次都復(fù)制整個(gè)數(shù)組,而只是共享同一個(gè)實(shí)例笨蚁,直到其中一個(gè)發(fā)生變化睹晒。這對(duì)我們應(yīng)用程序的性能非常有用。
不幸的是括细,并不是所有的值類型都有這種行為伪很。這意味著,如果我們有一個(gè)包含大量信息的結(jié)構(gòu)體奋单,我們可能需要一個(gè)寫時(shí)復(fù)制來(lái)提高我們應(yīng)用程序的性能并避免無(wú)用的副本锉试。因此,我們必須手動(dòng)創(chuàng)建此行為辱匿。
手動(dòng)寫時(shí)復(fù)制
讓我們考慮一個(gè)User
我們想要使用寫時(shí)復(fù)制的結(jié)構(gòu):
struct User {
var identifier = 1
}
我們必須開(kāi)始創(chuàng)建一個(gè)具有通用屬性的類T
键痛,它包裝了我們的值類型:
final class Ref<T> {
var value: T
init(value: T) {
self.value = value
}
}
我們使用class
——這是一種引用類型——因?yàn)楫?dāng)我們將一個(gè)引用類型分配給另一個(gè)引用類型時(shí)炫彩,兩個(gè)變量將共享同一個(gè)實(shí)例,而不是像值類型那樣復(fù)制它絮短。
然后江兢,我們可以創(chuàng)建一個(gè)struct
to wrap Ref
:
struct Box<T> {
private var ref: Ref<T>
init(value: T) {
ref = Ref(value: value)
}
var value: T {
get { return ref.value }
set {
guard isKnownUniquelyReferenced(&ref) else {
ref = Ref(value: newValue)
return
}
ref.value = newValue
}
}
}
因?yàn)?code>struct是值類型,所以當(dāng)我們將它分配給另一個(gè)變量時(shí)丁频,它的值被復(fù)制杉允,而屬性的實(shí)例ref
仍然由兩個(gè)副本共享,因?yàn)樗且粋€(gè)引用類型席里。
然后叔磷,我們第一次更改value
兩個(gè)Box
變量中的一個(gè)時(shí),我們創(chuàng)建了一個(gè)新實(shí)例`ref
guard isKnownUniquelyReferenced(&ref) else {
ref = Ref(value: newValue)
return
}
通過(guò)這種方式奖磁,這兩個(gè)Box
變量不再共享同一個(gè)ref
實(shí)例改基。
isKnownUniquelyReferenced返回一個(gè)布爾值,指示給定對(duì)象是否已知具有單個(gè)強(qiáng)引用咖为。
這里是整個(gè)代碼:
final class Ref<T> {
var value: T
init(value: T) {
self.value = value
}
}
struct Box<T> {
private var ref: Ref<T>
init(value: T) {
ref = Ref(value: value)
}
var value: T {
get { return ref.value }
set {
guard isKnownUniquelyReferenced(&ref) else {
ref = Ref(value: newValue)
return
}
ref.value = newValue
}
}
}
let user = User()
let box = Box(value: user)
var box2 = box // box2 shares instance of box.ref
box2.value.identifier = 2 // Creates new object for box2.ref
結(jié)論
這種方法的另一種替代方法是使用Array(而不是Box
)來(lái)包裝要在寫入時(shí)復(fù)制的值類型秕狰。不幸的是,使用Array的方法有一些缺點(diǎn)躁染。你可以在 Apple 的optimisation tips找到更多詳細(xì)信息鸣哀。