Swift-理解值類型

Equal 8年前發布 | 4K 次閱讀 Swift Apple Swift開發

在這里,我們要講講值類型和寫時復制。在 swift 的標準庫中,所有的集合類型都使用了寫時復制。我們在本篇文章中看一下寫時復制如何工作的,并且如何實現它。

引用類型

使用 swift 的 Data 和 NSMutableData 作對比

var sampleBytes: [UInt8] = [0x0b, 0xad, 0xf0, 0x0d]
let nsData = NSMutableData(bytes: sampleBytes, length: sampleBytes.count)

在這里,我們使用了 let 來修飾 nsData 變量,但是因為 NSMutableData 是一個引用類型,swift 的 let/var 關鍵字不能控制它。對于引用類型,let 只能保證 nsData 不被指向其他實例,但是我們可以修改他:

nsData.append(sampleBytes, length: sampleBytes.count)

因為我們操作的是一個對象,在以下例子中,兩個對象都會發生改變:

// 兩者都指向同一個對象,所以都改變了
let nsOtherData = nsData
nsData.append(sampleBytes, length: sampleBytes.count)

如果不希望 nsOtherData 也隨之改變,可以使用 mutableCopy

// 這樣 nsOtherData 就不會改變
let nsOtherData = nsData.mutableCopy() as! NSMutableData
nsData.append(sampleBytes, length: sampleBytes.count)

值類型

現在我們看一下 Data 類型,這是一個結構體

let data = Data(bytes: sampleBytes, count: sampleBytes.count)

這里我們使用了 let 修飾,這樣就不能修改 data 的值,除非將他設置為 var。并且將 data 賦值給別的變量,修改變量也不會影響 data。

值類型和引用類型之間的差異在于,當你將一個值類型賦值給別的變量或者作為函數參數時,只是對值進行了賦值。但是將引用類型分配給其他變量時,只會創建指向內存中同一個對象的第二個引用。

當我們創建一個副本時,結構體被逐個復制。但是這不意味著 Data 的值直接被復制過去,因為 Data 有一個內部的內存引用。當結構體被復制時,只是引用被復制給新值。只有當復制的變量值改變時,才會將值復制過去。

實現寫時復制

在這里我們將實現一個很簡單的結構體版本,來更好的理解寫時復制

struct MyData {
    var data = NSMutableData()

    mutating func append(_ bytes: [UInt8]) {
        data.append(bytes, length: bytes.count)
    }
}

接下來看看結果如何

var data = MyData()
var copy = data
data.append(sampleBytes)

Copy 還是被改變了,這是因為在結構體復制時,將引用復制了過去,這個引用指向了實際的值,所以 copy 還是被改變,以下代碼就可以修復這個問題

struct MyData {
    var data = NSMutableData()

    mutating func append(_ bytes: [UInt8]) {
        print("making a copy")
        data = data.mutableCopy() as! NSMutableData
        data.append(bytes, length: bytes.count)
    }
}

現在 copy 的值不會被改變了,接下來重構一下結構體,更加優雅點:

struct MyData {
    var data = NSMutableData()
    var dataForWriting: NSMutableData {
        mutating get {
            print("making a copy")
            data = data.mutableCopy() as! NSMutableData
            return data
        }
    }

    mutating func append(_ bytes: [UInt8]) {
        dataForWriting.append(bytes, length: bytes.count)
    }
}

讓寫時復制更高效

目前的做法是很幼稚的,我們會在每次 append 的時候都去復制,而不去管我們是這個對象的唯一擁有者,例如以下代碼:

for _ in 0..<10 {
    data.append(sampleBytes)
}
// This prints:
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy

以上代碼在理想情況下應該只 copy 一次,想這樣做的話也很簡單,使用 isKnownUniquelyReferenced 方法,這個方法可以判斷傳入的參數是否已經有一個強引用

struct MyData {
    var dataForWriting: NSMutableData {
        mutating get {
            if isKnownUniquelyReferenced(&data) {
                return data
            }
            print("making a copy")
            data = data.mutableCopy() as! NSMutableData
            return data
        }
    }
}

但是上面的代碼還是沒有起效的,因為 isKnownUniquelyReferenced 只適用于 swift 對象,現在我們來將 NSMutableData 包裝為 swift 對象:

final class Box<A> {
    // 使用這個常量
    let unbox: A
    init(_ value: A) {
        unbox = value
    }
}
struct MyData {
    var data = Box(NSMutableData())
    var dataForWriting: NSMutableData {
        mutating get {
            if isKnownUniquelyReferenced(&data) {
                return data.unbox
            }
            print("making a copy")
            data = Box(data.unbox.mutableCopy() as! NSMutableData)
            return data.unbox
        }
    }

    mutating func append(_ bytes: [UInt8]) {
        dataForWriting.append(bytes, length: bytes.count)
    }
}

現在我們再去進行之前的遍歷操作會發現只復制了一次,有點類似 lazy

寫時復制也不是時時起效

(0..<10).reduce(data) { result, _ in
    var copy = result
    copy.append(sampleBytes)
    return copy
}

上面的寫法會 copy10次,所以寫時復制也不是萬能的,但是一般情況下不會出現這種問題。

上面 reduce 內部做了什么?

  • (0..<10) 代表了閉包會進行10次
  • 接受了一個初始值參數,并將這個初始值作為第一次遍歷的 result 的值
  • 返回的 copy 作為下一次循環的 result 值

 

 

來自:http://www.jianshu.com/p/2adbcc8b6389

 

 本文由用戶 Equal 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!