【iOS開發(fā)】淺析Swift中的Copy-on-Write

什么是Copy-on-Write

寫入時復制(英語:Copy-on-write,簡稱COW)是一種計算機程序設(shè)計領(lǐng)域的優(yōu)化策略。其核心思想是,如果有多個調(diào)用者(callers)同時請求相同資源(如內(nèi)存或磁盤上的數(shù)據(jù)存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調(diào)用者試圖修改資源的內(nèi)容時,系統(tǒng)才會真正復制一份專用副本(private copy)給該調(diào)用者,而其他調(diào)用者所見到的最初的資源仍然保持不變。這過程對其他的調(diào)用者都是透明的。此作法主要的優(yōu)點是如果調(diào)用者沒有修改該資源,就不會有副本(private copy)被建立,因此多個調(diào)用者只是讀取操作時可以共享同一份資源。

在 Swift 中,Copy-on-Write(寫時復制)是一種優(yōu)化技術(shù),用于在需要進行修改時避免不必要的數(shù)據(jù)復制。它主要用于值類型(value types),如結(jié)構(gòu)體(struct)和枚舉(enum)。

在 Swift 中,當將一個值類型賦值給另一個變量或常量時,通常會發(fā)生值的復制。這意味著原始值的一個副本會被創(chuàng)建,并分配給新的變量或常量。這樣,原始值和副本是完全獨立的,對其中一個進行修改不會影響另一個。

然而,有時候進行這種復制操作是不必要的,特別是當值類型的實例是不可變的(immutable)或者只有一個引用時。為了避免不必要的復制開銷,Swift 使用了 Copy-on-Write 機制。

Copy-on-Write 的基本思想是,當一個不可變的值類型實例被復制時,實際上只會增加一個指向原始數(shù)據(jù)的引用計數(shù)。只有在進行修改操作時,才會對值進行復制,以確保修改操作不會影響到其他引用。

具體來說,當一個不可變的值類型實例被賦值給一個新的變量或常量時,原始值的引用計數(shù)會增加。這樣,原始值和新的變量或常量共享同一個內(nèi)存。當進行第一次修改操作時,Copy-on-Write 機制會檢查原始值的引用計數(shù)。如果引用計數(shù)為 1,表示該值沒有被共享,可以直接進行修改。但如果引用計數(shù)大于 1,表示該值被多個引用共享,此時會進行復制操作,創(chuàng)建一個新的副本,并將修改操作應用在副本上,而不是原始值上。

通過使用 Copy-on-Write 機制,Swift 可以避免不必要的復制開銷,提高性能和內(nèi)存效率。這種優(yōu)化技術(shù)在 Swift 的標準庫中被廣泛應用,特別是在Array、DictionarySet這樣的集合類型中。

需要注意的是,Copy-on-Write 僅適用于值類型(value types),對于引用類型(reference types)如類(class),它不會自動應用。對于引用類型,需要手動實現(xiàn)類似的行為,例如使用復制構(gòu)造函數(shù)(copy constructor)或提供自定義的復制方法。

下面,看看 Swift 中 COW 的具體體現(xiàn)。

基本數(shù)據(jù)類型

從下面的打印信息中我們可以看到,對于StringInt等基本類型的數(shù)據(jù)進行賦值時就發(fā)生了拷貝操作。

/// 打印地址
func address(of object: UnsafeRawPointer) {
    let addr = Int(bitPattern: object)
    print(String(format: "%p", addr))
}

var str1 = "1234"
var str2 = str1
address(of: &str1)  //0x100008108
address(of: &str2)  //0x100008118

var num1 = 5
var num2 = num1
address(of: &num1)  //0x100008128
address(of: &num2)  //0x100008130

集合類型

對于集合類型,如下面的arr1和arr2,我們可以看到在對寫入操作前,賦值操作并未發(fā)生拷貝操作;在對arr2進行修改(即寫入)后,arr2的地址發(fā)生變化,也就是說此時發(fā)生了拷貝操作。

var arr1 = [1,2,3,4]
var arr2 = arr1

//修改前,arr1和arr2地址一樣
address(of: &arr1)   //0x600001708420
address(of: &arr2)   //0x600001708420

//對arr2進行修改
arr2[2] = 0

//修改arr2后,arr2地址變了
address(of: &arr1)   //0x600001708420
address(of: &arr2)   //0x600001708460

自定義的結(jié)構(gòu)體

我們知道 Swift 中的 COW 適用于值類型數(shù)據(jù),而且通常是集合類型的。那么對于自定義的結(jié)構(gòu)體是否默認也存在這種機制呢?

struct MyStruct {
    var data: [Int]
}

var obj1 = MyStruct(data: [1, 2, 3])
var obj2 = obj1

address(of: &obj1)  //0x100008148
address(of: &obj2)  //0x100008150

obj2.data[0] = 100

address(of: &obj1)  //0x100008148
address(of: &obj2)  //0x100008150

從上面的打印可以看出,對于自定義的結(jié)構(gòu)體,并不支持COW。

COW的實現(xiàn)

在 Swift 的GitHub官方文檔OptimizationTips.rst中有這樣一段代碼:

/// 將實際值存于class Ref中,以便實現(xiàn)`reference type`
final class Ref<T> { //必須使用final修飾
    var val: T
    init(_ v: T) { val = v }
}

/// Box包裝`reference type`
struct Box<T> {
    var ref: Ref<T>
    init(_ x: T) { ref = Ref(x) }

    var value: T {
        get { return ref.val }
        set {
            //在進行寫操作前,檢查是否有其他引用,如果有,進行復制
            if !isKnownUniquelyReferenced(&ref) {
                ref = Ref(newValue)
                return
            }
            ref.val = newValue
        }
    }
}

struct TestCOW {
    var id: Int = 0
}

let test = TestCOW()

var box1 = Box(test)
var box2 = box1

address(of: &box1.ref.val)  //0x600000201570
address(of: &box2.ref.val)  //0x600000201570

box2.value.id = 1

address(of: &box1.ref.val)  //0x600000201570
address(of: &box2.ref.val)  //0x600000201d90

需要注意的是class Ref<T>必須使用final修飾,有以下幾個原因:

  • 避免類的繼承:將 Ref<T> 類定義為 final 可以防止其他類繼承它。由于 Ref<T> 類是用于支持 Box<T> 結(jié)構(gòu)體的內(nèi)部實現(xiàn),而不是作為可繼承的基類,因此將其定義為 final 可以確保它不會被錯誤地繼承和擴展。
  • 保持引用計數(shù)的一致性:Ref<T> 類使用 isKnownUniquelyReferenced(_:) 函數(shù)來檢查引用計數(shù),以確定是否需要進行復制。如果 Ref<T> 類是可繼承的,其他子類可能會引入對引用計數(shù)的修改,導致 isKnownUniquelyReferenced(_:) 函數(shù)的結(jié)果不準確。通過將 Ref<T> 類定義為 final,可以確保引用計數(shù)的一致性,從而正確地實現(xiàn) Copy-on-Write 的邏輯。

參考

維基百科中的寫入時復制

Use copy-on-write semantics for large values

Understanding Swift Copy-on-Write mechanisms

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

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

  • Swift 官方文檔在介紹 Collection Types 時,詳細解釋了 Copy-on-Write 機制 S...
    YannChee閱讀 346評論 0 0
  • 一.堆棧 棧是一塊空間較小但是運行速度很快的內(nèi)存區(qū)域,棧上的內(nèi)存分配遵循后進先出的原則,通過移動棧的尾指針實現(xiàn)pu...
    sidiWang閱讀 1,339評論 0 0
  • 什么是Copy-On-Write(寫時復制)? 我們將一個值類型分配給另一個值類型時,我們都有原始對象的副本: 如...
    sampson666888閱讀 1,056評論 0 3
  • 1. Swift 2. Objective-C 3. Swift VS Objective-C 4. Xcode ...
    四月_Hsu閱讀 2,379評論 0 16
  • 近期整理的iOS面試題。不定期更新中。如有問題,歡迎斧正。 派發(fā) Swift 有三種派發(fā)方式 1靜態(tài)派發(fā) 2消息派...
    程序狗旭旭旭閱讀 1,945評論 0 5

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