<
Value Type 也會在 Heap 裡嗎?
>
上一篇

Opaque Result Type
下一篇

重新檢視 Swift 的 Protocol(三)

最近和兩位志同道合的 iOS 發燒友(現在還有人用這詞嗎?)一起開辦了一個針對 iOS 開發者的中文 Podcast,第一集就榮幸地有大師來踢館,因為我們在節目裡提到 Swift 的 value types 都會在 stack 裡,大師 CJ 表示不對喔,若是 reference type 的 value property 或者被 closure 捕獲的 value type 都會在 heap 裡!

這就有趣了不追究不行,但現在還沒有能力看完 Swift run time 的 source code,只好硬著頭皮想了個湊合的辦法來實驗一下。

指向 Class Instance 的 Header Pointer

以前看指標文章時有玩過直接取得位址後,可以對位址內容硬改值以改變本該無法被 mutate 的東西(比如 let ) 所以我們先宣告一個 class ,建立一個 instance,我們知道這個 instance 實際上的內容會存在 heap 裡的一塊連續空間裡,然後我們搞一個指向它 instance 開頭的指標:

class Person {
  let name = "Pofat"
  let age = 18
  let married = true
}

let pofat = Person()

let headerPointer = Unmanaged.passUnretained(pofat as AnyObject).toOpaque() 

照理來說這個 instance 裡所有的 property 應該會依著宣告的順序依序擺在這個 headerPointer 指向的位址之後,不過 class 前面有兩個 byte 是用來放 descriptor 以及 retain count,所以 property 會從第三個 byte 開始,我們用 MemoryLayout 來取得各個 property 所佔的空間大小,依序找出 name, age 以及 married 的位址:

let nameOffset = 16
let ageOffset = nameOffset + MemoryLayout<String>.stride
let marriedOffset = ageOffset + MemoryLayout<Int>.stride

// Bound to the type of each property
let namePointer = haderPointer.advanced(by: nameOffset).assumingMemoryBound(to: String.self)
let agePointer = headerPointer.advanced(by: ageOffset).assumingMemoryBound(to: Int.self)
let marriedPointer = headerPointer.advanced(by: marriedOffset).assumingMemoryBound(to: Bool.self)

這些位址當然還是在 heap 裡,而且應該指到就是這些 value type 的 property 所在地(吧),我們直接強行改變這些位置上的值實驗看看:

namePointer.pointee = "Joe"
agePointer.pointee = 30
marriedPointer.pointee = false

print(pofat.name)       // get "Joe"
print(pofat.age)        // get 30
print(pofat.married)    // get false

OK,我們可以看到值如預料地變化了,這些 value type 真的是在 heap 裡啊! 也用了 Colab 做了一個可以直接在線上 run code 的版本

參考資料

  1. weak self 第一集:Swift API 設計之 Value Type 與 Reference Type
  2. 酄迎訂閱13、喬喬和我共同製作的 Podcast ,這裡有訂閱指南
  3. CJ Lin 大大不吝指教 與他推薦的文章
Top
Foot