在了解Swift發(fā)布調(diào)用機制之前煌集,先來了解下swift方法是如何保存的叁扫。
在swift中所有數(shù)據(jù)類型的無外乎兩種:值類型
,引用類型
地来。
值類型 : 在內(nèi)存中直接保存值乘寒,有點類似oc中taggedPointer
;
引用類型 : 在內(nèi)存中保存指針地址
編程語言 函數(shù)調(diào)用機制有三種:
- 直接調(diào)用
- 函數(shù)表調(diào)用
- 消息派發(fā)機制調(diào)用
函數(shù)調(diào)用機制是程序判斷使用哪種途徑去調(diào)用一個函數(shù)的機制万栅,每次函數(shù)調(diào)用時都會被觸發(fā)佑钾。
了解函數(shù)的調(diào)用機制,對于寫出高性能代碼來說十分有必要烦粒。
Java:默認函數(shù)表調(diào)用休溶,可以通過final修飾修改成直接派發(fā)。
C++:默認是直接調(diào)用扰她,但可以通過virtual修飾符來改成函數(shù)表派發(fā)
Objective-C:總是使用消息派發(fā)的機制兽掰,但允許開發(fā)者使用C直接派發(fā)來獲取性能的提高。
Swift 函數(shù)調(diào)用機制徒役,以上 三種都會涉及到孽尽。
調(diào)用方式:(Types of Dispatch)
程序派發(fā)的目的是為了告訴CPU需要被調(diào)用的函數(shù)在哪里。在我們深入理解Swift派發(fā)機制之前忧勿,先來了解下這三種派發(fā)機制杉女,以及沒中方式在動態(tài)性和性能之間的取舍。
直接調(diào)用:
直接派發(fā)是最快的鸳吸,不止是因為需要調(diào)用的指令集少宠纯,并且編譯器還有很大的優(yōu)化空間(比如:函數(shù)內(nèi)聯(lián))。直接派發(fā)也稱為靜態(tài)調(diào)用层释。
然而,對于編程來說直接調(diào)用也是最大的局限快集,而且因為缺乏動態(tài)性所以沒辦法支持繼承贡羔。
函數(shù)表調(diào)用
函數(shù)表派發(fā)是編譯型語言實現(xiàn)動態(tài)行為最常見的實現(xiàn)方式廉白。函數(shù)表使用了一個數(shù)組來儲存類聲明的每一個函數(shù)的指針。大部分語言把這個稱為『virtual table』虛函數(shù)表乖寒,Swift里稱為 『witness table』猴蹂。每一個類都會維護一個虛函數(shù)表,里邊記錄著類的所有函數(shù)楣嘁,如果父類被override的話磅轻,表里只會保存override之后的函數(shù),子類新增后會被插到這個數(shù)組的最后逐虚,運行時會決定這一個表實際要被調(diào)用的函數(shù)聋溜。
當一個函數(shù)調(diào)用時,首先要讀取函數(shù)表叭爱,在讀取函數(shù)對應的索引撮躁,然后跳轉(zhuǎn)。
消息派發(fā)機制調(diào)用
消息機制是調(diào)用函數(shù)的最動態(tài)的方式买雾,也是Cocoa的基礎把曼,這樣的機制催生了KVO、UIAppearence漓穿、CoreData等功能嗤军,這種運作方式的關鍵在于開發(fā)者可以在運行時改變函數(shù)的行為,不止可以通過swizzling 來改變晃危,還可以用 isa-swizzling修改對象的繼承關系叙赚,可以在面向?qū)ο蟮幕A上實現(xiàn)自定義派發(fā)。
Struct方法調(diào)用
示例代碼如下:
通過調(diào)試信息可知山害,內(nèi)存中只存儲了變量age纠俭,并未存儲方法。
打開匯編堆棧浪慌,可知funcDemo方法的地址0x10a242430
在代碼段中冤荆,其在符號表中的名稱為s11SwiftMethod9StructOneV8funcDemoyyF
(注:cat address指令需要安裝lldb擴展,點擊這里下載)
使用image list命令獲取到系統(tǒng)默認的地址偏移量0x000000010a23f000
使用方法地址0x10a242430
減去默認的地址偏移量0x000000010a23f000
权纤,得到該方法真實的地址0x3430
此時查看程序的MachO文件钓简,在TEXT段中查看0x3430
,可以看出右圖框中的部分即為方法funcDemo
的匯編代碼汹想。
再點擊Xcode的step into按鈕進入方法內(nèi)部并查看其匯編代碼外邓。
通過比較二者的匯編代碼,可知此時程序執(zhí)行的就是預先生成好的匯編代碼古掏。
再將在MachO文件的符號表中進行查找s11SwiftMethod9StructOneV8funcDemoyyF
损话,也可以知道其地址為0x3430
。
由此可以看出結(jié)構(gòu)體方法是在程序編譯鏈接時就直接在符號表中生成好,調(diào)用時無需額外操作丧枪。
按照同樣的步驟光涂,struct + protocol & struct + extension 也是直接調(diào)用。
枚舉類方法調(diào)用
定義如下枚舉類
打開匯編調(diào)試
由匯編指令可知拧烦,枚舉類的方法是直接調(diào)用
的忘闻。
Class 方法調(diào)用
定義如下類:
class ClassMethodModel {
func funcOne() {
print("funcOne")
}
func funcTwo() {
print("funcTwo")
}
}
let cm = ClassMethodModel()
cm.funcOne()
cm.funcTwo()
使用 xcrun swiftc -emit-sil ClassMethod.swift 得到 swift代碼 編譯后的 sil 產(chǎn)物,可以看出class的方法是存在vtable
中的恋博。
sil_vtable ClassMethodModel {
#ClassMethodModel.funcOne: (ClassMethodModel) -> () -> () : @$s11ClassMethod0aB5ModelC7funcOneyyF // ClassMethodModel.funcOne()
#ClassMethodModel.funcTwo: (ClassMethodModel) -> () -> () : @$s11ClassMethod0aB5ModelC7funcTwoyyF // ClassMethodModel.funcTwo()
#ClassMethodModel.init!allocator: (ClassMethodModel.Type) -> () -> ClassMethodModel : @$s11ClassMethod0aB5ModelCACycfC // ClassMethodModel.__allocating_init()
#ClassMethodModel.deinit!deallocator: @$s11ClassMethod0aB5ModelCfD // ClassMethodModel.__deallocating_deinit
}
借助之前分析Struct和Enum的經(jīng)驗齐佳,先來看一下class的匯編調(diào)試代碼
從圖中可以看出,rax即為變量 cm债沮,0x45e9
為funcOne的地址炼吴,0x45d9
為funcTwo的地址。
對此可以得出結(jié)論:swift的class方法調(diào)用不是直接調(diào)用秦士,而是對變量進行地址偏移后找到方法指針缺厉,再進行調(diào)用。
messageSend 調(diào)用
設計了如下Demo程序隧土,模擬OC調(diào)用Swift代碼提针。
查看其斷點處的匯編代碼:
可知OC調(diào)用swift @objc方法是通過objc_msgSend進行的。
反過來使用swift調(diào)用swift @objc方法曹傀,Demo代碼如下:
其斷點處的匯編代碼為:
根據(jù)匯編代碼辐脖,結(jié)合上文分析class方法調(diào)用的匯編代碼,可知swift調(diào)用swift @objc方法是函數(shù)表調(diào)用皆愉。
調(diào)用方式總結(jié)
Struct & Enum | Class | |
---|---|---|
普通方法 | 直接調(diào)用 | 函數(shù)表調(diào)用 |
protocol協(xié)議 | 直接調(diào)用 | 函數(shù)表調(diào)用 |
extension拓展 | 直接調(diào)用 | 直接調(diào)用 |
final | - | 直接調(diào)用 |
繼承方法 | - | 函數(shù)表調(diào)用 |
@objc | - | Swift調(diào)用Swift為函數(shù)表調(diào)用 嗜价,OC調(diào)用Swift為消息轉(zhuǎn)發(fā) |
dynamic | - | 函數(shù)表調(diào)用 |
@objc dynamic | - | objc_msgSend消息轉(zhuǎn)發(fā) |