Swift中熟悉的陌生人:Protocol(一)

weixin_33976072發表於2016-10-24

本文算是我對Swift基礎知識的一個再梳理,不會再討論基礎語法,而是把一些文件之外的東西,進行一次再梳理總結,方便查閱。

我們這裡首先來做一個分析,一個是普通的String物件,一個是遵守協議的String物件,這裡我們來對比一下它們各自佔用多大的記憶體?
這裡先羅列一下實驗的協議和擴充套件程式碼:

protocol TestProtocol { 
    var testData:NSURL? {get}
}

extension String:TestProtocol{
    var testData:NSURL? {
        return NSURL()
    }
}

然後我們開始書寫下面的測試程式碼:

var qurl:TestProtocol = "123"
print(MemoryLayout<TestProtocol>.size)  //40
var string:String = "123"
print(MemoryLayout<String>.size)    //24

我們可以在playground中看到結果,遵守protocol的qurl的大小為40,而不遵守任何協議的string的大小為24.
在Swift中,由於string是stuct結構體,而不再是OC中的class,所以不再是8個位元組,24個位元組的結構也可以用LLDB動態除錯type lookup String來檢視
,最後我們發現

 var _baseAddress: Swift.UnsafeMutableRawPointer?
 var _countAndFlags: Swift.UInt
 var _owner: AnyObject?

這三個屬性每個大小為8個位元組,加起來就是24位元組,那麼協議物件的40位元組又該如何解釋呢?
這裡我們首先檢視物件的地址,這裡我們首先寫一個列印地址的函式

func addrOf<T>( v:inout T){
  withUnsafePointer(to: &v) { print($0)}
}

addrOf(v: &qurl)

最後我這裡列印出結果,地址為0x00000001003dfb20
然後我們實用LLDB的動態除錯指令:x/5xg 0x00000001003dfb20
檢視其5個字長的記憶體空間
得出結果

0x1003dfb20: 0x00000001003397f8 0x0000000000000003
0x1003dfb30: 0x0000000000000000 0x00000001003ad878
0x1003dfb40: 0x000000010038a548

這裡繼續使用image lookup -a 0x00000001003397f8檢視第一個地址
我們可以發現值為

Address: SwiftTest[0x00000001003397f8] (SwiftTest.__TEXT.__cstring + 72)
      Summary: "123"

這第一個值其實存放的是遵守協議的字串的值,我們可以發現它是cstring
第二個存放字元個數和第三個地址為0,這兩個我們不管,直接解析第4個和第5個地址的內容。

image lookup -a 0x00000001003ad878
      Address: SwiftTest[0x00000001003ad878] (SwiftTest.__DATA.__const + 144264)
      Summary: SwiftTest`type metadata for Swift.String

我們可以發現第四個地址存放的是type metadata,即型別後設資料,型別後設資料即描述類的資料,有點類似Objective-C中的元類的作用

image lookup -a 0x000000010038a548
      Address: SwiftTest[0x000000010038a548] (SwiftTest.__DATA.__const + 88)
      Summary: SwiftTest`protocol witness table for Swift.String : SwiftTest.TestProtocol in SwiftTest

而第5個地址列印出味protocol witness table,即協議見證表,這個概念非常類似與Cpp中的vtable,即虛擬函式表。
那麼什麼是虛擬函式表呢?

712028-f6fa7092430ac8aa.png
6A8C58DD-4C1E-4DD4-B860-09DC27890748.png

在Swift,不同的結構、列舉、類都可以繼承協議,同樣的url屬性就會產生不同的getter方法。就像這張Cpp中的虛擬函式表一樣,不同vfunc1可能有不同的實現函式地址,所以需要有一個虛擬函式表來維護。
這裡我們繼續試驗:
我們發現第5個地址即指向虛擬函式表的地址,那個根據如圖所示,其實我們可以繼續解析這個虛擬函式表的地址的記憶體,繼續使用

x/xg 0x000000010038a548
0x10038a548: 0x0000000100002470

我們再看看看看這個虛擬函式表存放的地址的指令

x/i 0x0000000100002470
    0x100002470: 55  pushq  %rbp

看到pushq %rbp(這句表示:將呼叫函式的棧底壓棧到被調函式的棧中),我們就應該猜到這個地址存放了一個函式。
於是使用

image lookup -a 0x0000000100002470
     Address: SwiftTest[0x0000000100002470] (SwiftTest.__TEXT.__text + 2608)
     Summary: SwiftTest`protocol witness for SwiftTest.TestProtocol.testData.getter : Swift.Optional<__ObjC.NSURL> in conformance Swift.String : SwiftTest.TestProtocol in SwiftTest at TestDataConvertible.swift

我們就能發現在該支援實際存放了一個testData.getter方法。

712028-b4d0d30fa7ad6fe3.png
D49A102E-CA5C-46C6-8FCB-853A3CA9E4C5.png

如果有多個物件,那麼就會變成這樣的結構:

712028-a004dc60234545ee.png
542180AA-B179-4861-A80D-BA02413CC2CE.png

通過這樣的結構,也說明了為什麼協議只能儲存計算屬性而不能儲存 儲存屬性,這就是我對協議的理解,有更多關於協議的有趣內容,歡迎下方留言,與我分享。

相關文章