Swift 值類型使用Copy-On-Write

什么是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ì)信息鸣哀。

參考地址

https://www.marcosantadev.com/copy-write-swift-value-types/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吞彤,隨后出現(xiàn)的幾起案子我衬,更是在濱河造成了極大的恐慌,老刑警劉巖饰恕,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挠羔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡懂盐,警方通過(guò)查閱死者的電腦和手機(jī)褥赊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門糕档,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)莉恼,“玉大人,你說(shuō)我怎么就攤上這事速那±” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵端仰,是天一觀的道長(zhǎng)捶惜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)荔烧,這世上最難降的妖魔是什么吱七? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任汽久,我火速辦了婚禮,結(jié)果婚禮上踊餐,老公的妹妹穿的比我還像新娘景醇。我一直安慰自己,他們只是感情好吝岭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布三痰。 她就那樣靜靜地躺著,像睡著了一般窜管。 火紅的嫁衣襯著肌膚如雪散劫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天幕帆,我揣著相機(jī)與錄音获搏,去河邊找鬼。 笑死失乾,一個(gè)胖子當(dāng)著我的面吹牛颜凯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仗扬,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼症概,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了早芭?” 一聲冷哼從身側(cè)響起彼城,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎退个,沒(méi)想到半個(gè)月后募壕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡语盈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年舱馅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刀荒。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡代嗤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缠借,到底是詐尸還是另有隱情干毅,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布泼返,位于F島的核電站硝逢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渠鸽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一叫乌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧徽缚,春花似錦综芥、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至红省,卻和暖如春额各,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吧恃。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工虾啦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痕寓。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓傲醉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親呻率。 傳聞我的和親對(duì)象是個(gè)殘疾皇子硬毕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容