在Swift中清笨,如果你具有較大的值類型對象或數(shù)據(jù)并且必須將其作為參數(shù)分配或傳遞給一個函數(shù)娩缰,則在性能方面復(fù)制它代價可能是非常昂貴的饭入,因為必須將所有基礎(chǔ)數(shù)據(jù)復(fù)制到內(nèi)存中的其他位置惦积。Advice: Use copy-on-write semantics for large values胁住,蘋果建議當復(fù)制大的值類型數(shù)據(jù)的時候五芝,使用寫時復(fù)制技術(shù)痘儡,那什么是寫時復(fù)制呢?我們現(xiàn)在看一段代碼:
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(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
array2.append(4)
print(address: array2) //0x6000000aa100
我們看到當array2
的值沒有發(fā)生變化的時候与柑,array1
和array2
指向同一個地址谤辜,但是當array2
的發(fā)生變化時,array2
指向地址也變了价捧,很奇怪是吧丑念。
《Advanced Swift》關(guān)于寫時復(fù)制解釋的非常好:
在 Swift 標準庫中,像是 Array结蟋,Dictionary 和 Set 這樣的集合類型是通過一種叫做寫時復(fù)制 (copy-on-write) 的技術(shù)實現(xiàn)的脯倚。我們這里有一個整數(shù)數(shù)組:
var x = [1,2,3]
vary=x
如果我們創(chuàng)建了一個新的變量 y,并且把 x 賦值給它時嵌屎,會發(fā)生復(fù)制推正,現(xiàn)在 x 和 y 含有的是獨立
的結(jié)構(gòu)體:
vary=x
在內(nèi)部,這些 Array 結(jié)構(gòu)體含有指向某個內(nèi)存的引用宝惰。這個內(nèi)存就是數(shù)組中元素所存儲的位置植榕。 兩個數(shù)組的引用指向的是內(nèi)存中同一個位置,這兩個數(shù)組共享了它們的存儲部分尼夺。不過尊残,當我 們改變 x 的時候,這個共享會被檢測到淤堵,內(nèi)存將會被復(fù)制寝衫。這樣一來,我們得以獨立地改變兩個 變量拐邪。昂貴的元素復(fù)制操作只在必要的時候發(fā)生慰毅,也就是我們改變這兩個變量的時候發(fā)生復(fù)制:
x.append(5)
y.removeLast()
x // [1, 2, 3, 5]
y // [1, 2]
這種行為就被稱為寫時復(fù)制。它的工作方式是扎阶,每當數(shù)組被改變汹胃,它首先檢查它對存儲緩沖區(qū) 的引用是否是唯一的婶芭,或者說,檢查數(shù)組本身是不是這塊緩沖區(qū)的唯一擁有者统台。如果是雕擂,那么 緩沖區(qū)可以進行原地變更;也不會有復(fù)制被進行。不過贱勃,如果緩沖區(qū)有一個以上的持有者 (如本 例中)井赌,那么數(shù)組就需要先進行復(fù)制,然后對復(fù)制的值進行變化贵扰,而保持其他的持有者不受影響仇穗。
作為一個結(jié)構(gòu)體的作者,你并不能免費獲得寫時復(fù)制的行為戚绕,你需要自己進行實現(xiàn)纹坐。當你自己的類型內(nèi)部含有一個或多個可變引用,同時你想要保持值語義時舞丛,你應(yīng)該為其實現(xiàn)寫時復(fù)制耘子。
為了維護值語義,通常都需要進行在每次變更時球切,都進行昂貴的復(fù)制操作谷誓,但是寫時復(fù)制技術(shù)
避免了在非必要的情況下的復(fù)制操作。
蘋果在Advice: Use copy-on-write semantics for large values中教我們怎么去使用 copy-on-write 技術(shù)吨凑。
我們使用class
捍歪,這是一個引用類型,因為當我們將引用類型分配給另一個時鸵钝,兩個變量將共享同一個實例糙臼,而不是像值類型一樣復(fù)制它。
final class Ref<T> {
var value: T
init(value: T) {
self.value = value
}
}
然后恩商,我們可以創(chuàng)建一個struct
包裝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
}
}
}
由于struct是一個值類型变逃,當我們將它分配給另一個變量時,它的值被復(fù)制怠堪,而屬性ref的實例仍由兩個副本共享韧献,因為它是一個引用類型。
然后研叫,我們第一次更改兩個Box變量的值時,我們創(chuàng)建了一個新的ref實例璧针,這要歸功于:
guard isKnownUniquelyReferenced(&ref) else {
ref = Ref(value: newValue)
return
}
這樣嚷炉,兩個Box變量不再共享相同的ref實例。
為了提供高效的寫時復(fù)制特性探橱,我們需要知道一個對象是否是唯一的申屹。如果它是唯一引用绘证,那么我們就可以直接原地修改對象。否則哗讥,我們需要在修改前創(chuàng) 建對象的復(fù)制嚷那。在 Swift 中,我們可以使用
isKnownUniquelyReferenced
函數(shù)來檢查某個引 用只有一個持有者杆煞。如果你將一個 Swift 類的實例傳遞給這個函數(shù)魏宽,并且沒有其他變量強引用 這個對象的話,函數(shù)將返回true
决乎。如果還有其他的強引用队询,則返回false
。不過构诚,對于 Objective-C 的類蚌斩,它會直接返回 false。
比如我們想在一個使用struct
類型的User中使用copy-on-write的:
struct User {
var identifier = 1
}
let user = User()
let box = Box(value: user)
var box2 = box // box2 shares instance of box.ref
box2.value.identifier = 2
Advice: Use copy-on-write semantics for large values
When to Use Value Types and Reference Types in Swift
Use Copy-On-Write With Swift Value Types