寫時(shí)復(fù)制

寫時(shí)復(fù)制

在swift中,像Array、Dictionary、Set等集合類型都是通過(guò)寫時(shí)復(fù)制(copy-on-write)技術(shù)實(shí)現(xiàn)的。

例如一個(gè)整形數(shù)組

let x = [1,2,3,4]
var y = x

創(chuàng)建新變量 y,把 x 賦值給 y 會(huì)發(fā)生復(fù)制,現(xiàn)在x和y都是獨(dú)立的結(jié)構(gòu)體。然后這些Array 結(jié)構(gòu)體含有指向某個(gè)內(nèi)存的引用。這個(gè)內(nèi)存就是數(shù)組元素存儲(chǔ)在堆上的位置。我們可以通過(guò) swift Array 的

@inlinable public func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R

來(lái)獲取 Array 元素存儲(chǔ)的地址指針,我們可以看出數(shù)組x和y存儲(chǔ)地址和元素確實(shí)都是相同的

let xPoints1 = x.withUnsafeBufferPointer{$0}
let yPoints1 = y.withUnsafeBufferPointer{$0}

print(xPoints1, x)  // UnsafeBufferPointer(start: 0x000060000143b1e0, count: 4) [1, 2, 3, 4]
print(yPoints1, y)  // UnsafeBufferPointer(start: 0x000060000143b1e0, count: 4) [1, 2, 3, 4]

然后我們對(duì)數(shù)組 y 進(jìn)行操作:

let xPoints2 = x.withUnsafeBufferPointer{$0}
let yPoints2 = y.withUnsafeBufferPointer{$0}

y.append(5)
let xPoints2 = x.withUnsafeBufferPointer{$0}
let yPoints2 = y.withUnsafeBufferPointer{$0}
print(xPoints2, x) // UnsafeBufferPointer(start: 0x000060000143b1e0, count: 4) [1, 2, 3, 4]
print(yPoints2, y) // UnsafeBufferPointer(start: 0x0000600002516a80, count: 5) [1, 2, 3, 4, 5]

通過(guò)以上代碼可以看出,當(dāng)y引用的存儲(chǔ)要改變時(shí),存儲(chǔ)進(jìn)行了復(fù)制。這就是所謂的"寫時(shí)復(fù)制"。

但是作為一個(gè)結(jié)構(gòu)體的作者,你不能免費(fèi)獲得這種特性,你需要自己進(jìn)行實(shí)現(xiàn)。當(dāng)結(jié)構(gòu)類型內(nèi)部含有一個(gè)或者多個(gè)可變引用,同時(shí)想要保持值語(yǔ)義,并且不必要的復(fù)制,為你的類型實(shí)現(xiàn)寫時(shí)復(fù)制是有意義的。

接下來(lái),我們用 NSMutableArray 作為內(nèi)部引用類型來(lái)實(shí)現(xiàn) swift 中的值類型的 Array 結(jié)構(gòu)體。

寫實(shí)復(fù)制(昂貴模式)

我們首先將 _array 標(biāo)記為結(jié)構(gòu)體的私有屬性。我們不再直接變更 _array,而
是通過(guò)一個(gè)計(jì)算屬性 _arrayForWriting 來(lái)訪問(wèn)它。這個(gè)計(jì)算屬性總是會(huì)復(fù)制 _array 并將其返回:

struct MyArray {
    fileprivate var _array: NSMutableArray
    fileprivate var _arrayForWriting: NSMutableArray {
        mutating get {
            _array = _array.mutableCopy() as! NSMutableArray
            return _array
        }
    }
    
    init(_ array: NSMutableArray) {
        self._array = array.mutableCopy() as! NSMutableArray
    }
    
    mutating func add(_ other: MyArray) {
        _arrayForWriting.addObjects(from: other._array as Array)
    }
    
    mutating func append(_ any: Any) {
        _arrayForWriting.add(any)
    }
    
    var description: String {
        return (_array as Array).description
    }
    
}

我們通過(guò)get方法操作時(shí),每次均復(fù)制 _array,這個(gè)結(jié)構(gòu)體具有值語(yǔ)義了。但這樣的做法雖然有效,但是當(dāng)我們多次改變同一個(gè)變量時(shí),效率很低,因?yàn)槊看尾僮鳎紩?huì)通過(guò) get 方法去復(fù)制 _array得到 _arrayForWriting。比如如下操作:

var array = MyArray(NSArray(array: [1, 2, 3, 4]))

for index in 5...9 {
    array.append(index)
}

每次 append 都會(huì)需要調(diào)用 get 方法去復(fù)制 _array 。如果我們能讓 _array 在沒(méi)有被共享之前,對(duì)它進(jìn)行原地變更就高效了。

寫時(shí)復(fù)制(高效方式)

上述問(wèn)題說(shuō)道 _array 沒(méi)有被共享之前,那就是我們要在它是唯一引用的時(shí)候進(jìn)行原地修改,如果有別的變量也在強(qiáng)引用它,那么我們就需要復(fù)制 _array 后去再修改它了。在 Swift 中,提供了 isKnownUniquelyReferenced 函數(shù)來(lái)檢查引用的唯一性,沒(méi)有其他強(qiáng)應(yīng)用將返回 true。由于對(duì)于 Objective-C 的類,它會(huì)直 接返回 false,所以我們可以創(chuàng)建一個(gè) Swift 類,來(lái)封裝 Objective-C 對(duì)象到 Swift類中。

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

現(xiàn)在我們改造之前寫的 MyArray

 struct MyArray {

    private var _data: Box<NSMutableArray>
    private var _dataForWriting: NSMutableArray {
        mutating get {

            if !isKnownUniquelyReferenced(&_data) {
                _data = Box(_data.uniqueValue.mutableCopy() as! NSMutableArray)
                print("Making a copy")
            }
            return _data.uniqueValue
        }
    }

    init(_ array: NSArray) {
        self._data = Box(array.mutableCopy() as! NSMutableArray)
    }

    mutating func append(_ other: MyArray) {
        _dataForWriting.addObjects(from: other._data.uniqueValue as Array)
    }

    mutating func append(_ any: Any) {
        _dataForWriting.add(any)
    }

    var description: String {
        return (_data.uniqueValue as Array).description
    }
}

現(xiàn)在我們可以測(cè)試一下:

static func testMyArray() {
    var x = MyArray(NSArray(array: [1, 2, 3, 4]))
    // 原地操作
    x.append(5)
    var y = x
    print(x.description, y.description)  // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
        
    y.append(6)  // Making a copy
    print(x.description, y.description)  // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6]
}

原地操作不會(huì)發(fā)生復(fù)制,而我們賦值給 y 后,對(duì)應(yīng)進(jìn)行操作,此時(shí)發(fā)生了復(fù)制。至此寫時(shí)復(fù)制已經(jīng)完成

對(duì)于有的書上說(shuō):Array 和 Dictionary下標(biāo)取值值元素有不同,通過(guò)下標(biāo)操作其實(shí)一樣是會(huì)進(jìn)行復(fù)制:

static func testTrap() {   
    let arrayForDict = MyArray(NSArray(array: [1, 2, 3, 4]))
        
    var dict: [String: MyArray] = ["s": arrayForDict]
    dict["s"]!.append(6)  // Making a copy
         
    let arrayForArray = MyArray(NSArray(array: [5, 6, 7, 8]))
    var array = [arrayForArray]
    array[0].append(9)  // Making a copy
}
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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