探究寫時復(fù)制

寫時復(fù)制

和Objective-C不同陶珠,在Swift中畜隶,Array犯犁、Dictionary属愤、Set這樣的集合不再是引用類型而是值類型了,這意味著酸役,每次傳遞不再是傳遞指針而是一個Copy后的值住诸,但是如果每次都要Copy一次的話就會太浪費性能,所以這時候就要用到一個寫時復(fù)制(copy-or-write)的技術(shù)涣澡。

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

在內(nèi)部只壳,這些Array的結(jié)構(gòu)體含有指向某個內(nèi)存的引用。這個內(nèi)存就是數(shù)組中元素所存儲的位置暑塑。兩個數(shù)組的引用指向的是內(nèi)存中同一個位置,這兩個數(shù)據(jù)共享了它們的存儲部分锅必。當我們改變 x 的時候事格,這個共享會被檢測到,內(nèi)存將會被復(fù)制搞隐。所以說驹愚,復(fù)制操作只會則必要的時候發(fā)生。

這種行為被稱為寫時復(fù)制劣纲。它的工作方式是逢捺,每當數(shù)組被改變,它首先檢查它對存儲緩沖區(qū)的引用是否是唯一癞季,或者說劫瞳,檢查數(shù)組本身是不是這塊緩沖區(qū)的唯一擁有者。如果是绷柒,那么緩沖區(qū)可以進行原地變更志于;也不會有復(fù)制被進行。如果緩沖區(qū)有一個以上的持有者废睦,那么數(shù)組就需要先進行復(fù)制伺绽,然后對復(fù)制的值進行變化,而保持其他的持有者不受影響。

實現(xiàn)寫時復(fù)制

使用 NSMutableData 作為內(nèi)部引用類型來實現(xiàn) Data 結(jié)構(gòu)體奈应。

struct MyData {
    var _data: NSMutableData
    var flag: String?
    init(_ data: NSData) {
        _data = data.mutableCopy() as! NSMutableData
    }
}

extension MyData {
    func append(_ byte: UInt8) {
        var mutableByte = byte
        _data.append(&mutableByte, length: 1)
    }
}

let theData = NSData(base64Encoded: "wAEP/w==")!
var x = MyData(theData)
x.flag = "flag"
var y = x
x._data == y._data
y.flag = "new flag"

x.append(0x55)
print(x)    // MyData(_data: <c0010fff 55>, flag: Optional("flag"))
print(y)    // MyData(_data: <c0010fff 55>, flag: Optional("new flag"))

MyData雖然是一個結(jié)構(gòu)體澜掩,是一個值類型,對于值類型數(shù)據(jù)遵循寫時復(fù)制的特性杖挣,但是對于內(nèi)部 NSMutableData 這樣的引用類型肩榕,多個 MyData 的變量指向的還是同一個 NSMutableData 地址。所以我們要手動實現(xiàn) NSMutableData 的寫時復(fù)制

簡單的實現(xiàn)
struct MyData {
    fileprivate var _data: NSMutableData
    fileprivate var _dataForWriting: NSMutableData {
        mutating get {
            _data = _data.mutableCopy() as! NSMutableData
            return _data
        }
    }
    var flag: String?
    
    init() {
        _data = NSMutableData()
    }
    
    init(_ data: NSData) {
        _data = data.mutableCopy() as! NSMutableData
    }
}

extension MyData {
    mutating func append(_ byte: UInt8) {
        var mutableByte = byte
        _dataForWriting.append(&mutableByte, length: 1)
    }
}

不直接變更 _data程梦,通過一個 _dataForWriting 來訪問点把。每次都會復(fù)制 _data 并將該復(fù)制返回。當我們調(diào)用 append 時屿附,將會進行復(fù)制

let theData = NSData(base64Encoded: "wAEP/w==")!
var x = MyData(theData)
x.flag = "flag"
var y = x
x._data == y._data
y.flag = "new flag"

x.append(0x55)

print(x)    // MyData(_data: <c0010fff 55>, flag: Optional("flag"))
print(y)    // MyData(_data: <c0010fff>, flag: Optional("new flag"))

但是這樣有一個問題郎逃,多次 append 時,就會非常浪費挺份,因為每次都要 copy

高效的方式

我們可以通過判斷一個對象是否是唯一的引用褒翰,來決定是否需要對這個對象進行復(fù)制。如果它是唯一引用匀泊,那就直接修改對象优训,否則,需要在修改前創(chuàng)建該對象的復(fù)制各聘。
在 Swift 中揣非,通過 isKnownUniquelyReferenced 函數(shù)來檢查某個引用只有一個持有者。只有一個返回 true躲因,否則返回 false早敬。對于 OC 類,它會直接返回 false大脉,我們需要創(chuàng)建一個 Swift 的類來包裝 OC 類

final class Box<A> {
    var unbox: A
    init(_ value: A) {
        self.unbox = value
    }
}

var x = Box(NSMutableData())
isKnownUniquelyReferenced(&x)   // true

var y = x
isKnownUniquelyReferenced(&y)   // false

讓我們再寫一個循環(huán)

struct MyData {
    fileprivate var _data: Box<NSMutableData>
    fileprivate var _dataForWriting: NSMutableData {
        mutating get {
            if !isKnownUniquelyReferenced(&_data) {
                _data = Box(_data.unbox.mutableCopy() as! NSMutableData)
                print("Making a copy")
            }
            return _data.unbox
        }
    }
    
    init() {
        _data = Box(NSMutableData())
    }
    
    init(_ data: NSData) {
        _data = Box(data.mutableCopy() as! NSMutableData)
    }
}

extension MyData {
    mutating func append(_ byte: UInt8) {
        var mutableByte = byte
        _dataForWriting.append(&mutableByte, length: 1)
    }
}

var bytes = MyData()
var copy = bytes
for byte in 0..<5 as CountableRange<UInt8> {
    print("Appending 0x\(String(byte, radix: 16))")
    bytes.append(byte)
}
print(bytes)
print(copy)

/*
Appending 0x0
Making a copy
Appending 0x1
Appending 0x2
Appending 0x3
Appending 0x4
MyData(_data: __lldb_expr_26.Box<__C.NSMutableData>)
MyData(_data: __lldb_expr_26.Box<__C.NSMutableData>)
*/

可以看到當?shù)谝淮?append 的時候搞监,拷貝了一份引用,之后因為新拷貝的引用是惟一的镰矿,就沒有進行復(fù)制操作

來自 https://leejnull.github.io/2020/01/03/2020-01-03-02/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琐驴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子秤标,更是在濱河造成了極大的恐慌绝淡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苍姜,死亡現(xiàn)場離奇詭異够委,居然都是意外死亡,警方通過查閱死者的電腦和手機怖现,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門茁帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玉罐,“玉大人,你說我怎么就攤上這事潘拨〉跏洌” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵铁追,是天一觀的道長季蚂。 經(jīng)常有香客問我,道長琅束,這世上最難降的妖魔是什么扭屁? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮涩禀,結(jié)果婚禮上料滥,老公的妹妹穿的比我還像新娘。我一直安慰自己艾船,他們只是感情好葵腹,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屿岂,像睡著了一般践宴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爷怀,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天阻肩,我揣著相機與錄音,去河邊找鬼运授。 笑死磺浙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的徒坡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瘤缩,長吁一口氣:“原來是場噩夢啊……” “哼喇完!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起剥啤,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤锦溪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后府怯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刻诊,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年牺丙,在試婚紗的時候發(fā)現(xiàn)自己被綠了则涯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片复局。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粟判,靈堂內(nèi)的尸體忽然破棺而出亿昏,到底是詐尸還是另有隱情,我是刑警寧澤档礁,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布角钩,位于F島的核電站,受9級特大地震影響呻澜,放射性物質(zhì)發(fā)生泄漏递礼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一羹幸、第九天 我趴在偏房一處隱蔽的房頂上張望脊髓。 院中可真熱鬧,春花似錦睹欲、人聲如沸供炼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袋哼。三九已至,卻和暖如春闸衫,著一層夾襖步出監(jiān)牢的瞬間涛贯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工蔚出, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弟翘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓骄酗,卻偏偏與公主長得像稀余,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趋翻,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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