Swift寫(xiě)時(shí)復(fù)制(copy-on-write)

在Swift中,如果你具有較大的值類(lèi)型對(duì)象或數(shù)據(jù)并且必須將其作為參數(shù)分配或傳遞給一個(gè)函數(shù),則在性能方面復(fù)制它代價(jià)可能是非常昂貴的,因?yàn)楸仨殞⑺谢A(chǔ)數(shù)據(jù)復(fù)制到內(nèi)存中的其他位置。Advice: Use copy-on-write semantics for large values,蘋(píng)果建議當(dāng)復(fù)制大的值類(lèi)型數(shù)據(jù)的時(shí)候,使用寫(xiě)時(shí)復(fù)制技術(shù),那什么是寫(xiě)時(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

我們看到當(dāng)array2的值沒(méi)有發(fā)生變化的時(shí)候,array1array2指向同一個(gè)地址,但是當(dāng)array2的發(fā)生變化時(shí),array2指向地址也變了,很奇怪是吧。

《Advanced Swift》關(guān)于寫(xiě)時(shí)復(fù)制解釋的非常好:

在 Swift 標(biāo)準(zhǔn)庫(kù)中,像是 Array,Dictionary 和 Set 這樣的集合類(lèi)型是通過(guò)一種叫做寫(xiě)時(shí)復(fù)制 (copy-on-write) 的技術(shù)實(shí)現(xiàn)的。我們這里有一個(gè)整數(shù)數(shù)組:

var x = [1,2,3] 
vary=x

如果我們創(chuàng)建了一個(gè)新的變量 y,并且把 x 賦值給它時(shí),會(huì)發(fā)生復(fù)制,現(xiàn)在 x 和 y 含有的是獨(dú)立
的結(jié)構(gòu)體:

vary=x

在內(nèi)部,這些 Array 結(jié)構(gòu)體含有指向某個(gè)內(nèi)存的引用。這個(gè)內(nèi)存就是數(shù)組中元素所存儲(chǔ)的位置。 兩個(gè)數(shù)組的引用指向的是內(nèi)存中同一個(gè)位置,這兩個(gè)數(shù)組共享了它們的存儲(chǔ)部分。不過(guò),當(dāng)我 們改變 x 的時(shí)候,這個(gè)共享會(huì)被檢測(cè)到,內(nèi)存將會(huì)被復(fù)制。這樣一來(lái),我們得以獨(dú)立地改變兩個(gè) 變量。昂貴的元素復(fù)制操作只在必要的時(shí)候發(fā)生,也就是我們改變這兩個(gè)變量的時(shí)候發(fā)生復(fù)制:

x.append(5) 
y.removeLast() 
x // [1, 2, 3, 5]
y // [1, 2]

這種行為就被稱(chēng)為寫(xiě)時(shí)復(fù)制。它的工作方式是,每當(dāng)數(shù)組被改變,它首先檢查它對(duì)存儲(chǔ)緩沖區(qū) 的引用是否是唯一的,或者說(shuō),檢查數(shù)組本身是不是這塊緩沖區(qū)的唯一擁有者。如果是,那么 緩沖區(qū)可以進(jìn)行原地變更;也不會(huì)有復(fù)制被進(jìn)行。不過(guò),如果緩沖區(qū)有一個(gè)以上的持有者 (如本 例中),那么數(shù)組就需要先進(jìn)行復(fù)制,然后對(duì)復(fù)制的值進(jìn)行變化,而保持其他的持有者不受影響。

作為一個(gè)結(jié)構(gòu)體的作者,你并不能免費(fèi)獲得寫(xiě)時(shí)復(fù)制的行為,你需要自己進(jìn)行實(shí)現(xiàn)。當(dāng)你自己的類(lèi)型內(nèi)部含有一個(gè)或多個(gè)可變引用,同時(shí)你想要保持值語(yǔ)義時(shí),你應(yīng)該為其實(shí)現(xiàn)寫(xiě)時(shí)復(fù)制。
為了維護(hù)值語(yǔ)義,通常都需要進(jìn)行在每次變更時(shí),都進(jìn)行昂貴的復(fù)制操作,但是寫(xiě)時(shí)復(fù)制技術(shù)
避免了在非必要的情況下的復(fù)制操作。

蘋(píng)果在Advice: Use copy-on-write semantics for large values中教我們?cè)趺慈ナ褂?copy-on-write 技術(shù)。

我們使用class,這是一個(gè)引用類(lèi)型,因?yàn)楫?dāng)我們將引用類(lèi)型分配給另一個(gè)時(shí),兩個(gè)變量將共享同一個(gè)實(shí)例,而不是像值類(lèi)型一樣復(fù)制它。

final class Ref<T> {
    var value: T
    init(value: T) {
        self.value = value
    }
}

然后,我們可以創(chuàng)建一個(gè)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是一個(gè)值類(lèi)型,當(dāng)我們將它分配給另一個(gè)變量時(shí),它的值被復(fù)制,而屬性ref的實(shí)例仍由兩個(gè)副本共享,因?yàn)樗且粋€(gè)引用類(lèi)型。

然后,我們第一次更改兩個(gè)Box變量的值時(shí),我們創(chuàng)建了一個(gè)新的ref實(shí)例,這要?dú)w功于:

guard isKnownUniquelyReferenced(&ref) else {
    ref = Ref(value: newValue)
    return
}

這樣,兩個(gè)Box變量不再共享相同的ref實(shí)例。

為了提供高效的寫(xiě)時(shí)復(fù)制特性,我們需要知道一個(gè)對(duì)象是否是唯一的。如果它是唯一引用,那么我們就可以直接原地修改對(duì)象。否則,我們需要在修改前創(chuàng) 建對(duì)象的復(fù)制。在 Swift 中,我們可以使用 isKnownUniquelyReferenced 函數(shù)來(lái)檢查某個(gè)引 用只有一個(gè)持有者。如果你將一個(gè) Swift 類(lèi)的實(shí)例傳遞給這個(gè)函數(shù),并且沒(méi)有其他變量強(qiáng)引用 這個(gè)對(duì)象的話(huà),函數(shù)將返回 true。如果還有其他的強(qiáng)引用,則返回 false。不過(guò),對(duì)于 Objective-C 的類(lèi),它會(huì)直接返回 false。

比如我們想在一個(gè)使用struct類(lèi)型的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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容