Swift 函數派發的一點了解

題外話

那天和羣裏一我的聊天,他去過頭條京東面試,用的 Swift,問的問題我都沒都聽過,其中就有這個 Swift 派發機制。決定仍是看下這些基礎原理,而後忽然想到一個問題,爲何要寫博客呢,如今的想法是和別人交流的時候能邏輯很清晰的解釋什麼是 派發機制,否則以個人慣性,可能時間過一點就說的迷迷糊糊了。看了幾篇文章,這兩篇說得不錯。git

kemchenj.github.io/2016-12-25-…github

zhaoxinyu.me/2018-04-08-…面試

www.jianshu.com/p/cfe7da018…swift

下面文章寫的有點怪,是先帶着結果看問題的。由於這也是我最開始的思路,和其餘博客不太同樣,我並非先遇到問題。這個話題是我想了解因此就直接查的問題,得出結論,而後根據結論分析一些代碼例子,最後一步是提供驗證這些結論是否正確的方法,由於最後一步比較乏味。ide

派發機制

經過閱讀上面兩篇文章和 SIL 代碼驗證事後,swift 有四種派發方式:函數

  • static dispatch:靜態派發(直接派發)
  • table dispatch:函數表派發, 經過 SIL 分析,swift 中有兩種函數表,協議用的 witness_table,類用的 virtual_table
  • message dispatch:消息派發,OC 中經常使用的派發方式

第一篇文章中關於這個部分說的比較詳細,大段抄過來不太好。。優化

總結

派發機制總結表(我沒驗證全,用 SIL 驗證了部分,剩餘是看上面的文章的)ui

  1. 表裏左邊的便是聲明引用類型,非實際對象類型;
  2. 根據 SIL 在非編譯器優化的狀況下得出的結論,有的狀況下會把 table 派發優化成直接派發,不在討論狀況中

dispatch.png

圖中那個問好是我實踐的和其餘文章說的不同,最下面有說spa

除上圖外的重要點:翻譯

  • 引用的類型決定了派發的方式。
  • NSObjectClass 和 Class 沒什麼區別(至少編譯器未優化前是這樣)

根據結論用代碼看問題

根據以上邏輯,有兩個代碼問題,剛看的時候有點疑惑,如今能夠用上面的東西解釋下

  1. 先看一個簡單的邏輯
protocol MyProtocol {}

extension MyProtocol {
    func testFuncA() {
        print("protocol - funcA")
    }
}

class Myclass: MyProtocol {
    func testFuncA() {
        print("class - funcA")
    }
}

let x: MyProtocol = Myclass()
x.testFuncA() // protocol - funcA
複製代碼

因爲 x 是協議類型,因此先會去查協議的函數表,而協議裏並無 funcA 的聲明,因此協議函數表裏就不存在這個方法,也不會去根據表查找實現,就走了協議的 extension 的直接派發。


  1. 再看另外一個狀況
protocol MyProtocol {
	func testFuncA()
}
... (和上面同樣)

let x: MyProtocol = Myclass()
x.testFuncA() // class - funcA
複製代碼

因爲協議函數表裏聲明瞭 funcA,因此用協議函數表查找實現,找到了在 Myclass 中的實現,走的函數表派發。


第三個問題 是上述文章提到的 SR-103

protocol Greetable {
    func sayHi()
}

extension Greetable {
    func sayHi() {
        print("Hello")
    }
}

class Person: Greetable {}

class LoudPerson: Person {
    func sayHi() {
        print("HELLO")
    }
}

let x: Greetable = LoudPerson()
x.sayHi() // Hello,protocol

複製代碼

這個被肯定是一個bug。按照正常的理解,協議函數表會查找子類的 sayhi 實現,可是實際上只找了遵循協議的那個 person 類,找不到,因此就走了協議 extension 的直接派發。可是三年過去了,這個bug好像並沒被修復。。

SIL 驗證過程

Swift Intermediate Language(SIL) 是 Swift 在編譯過程當中的中間產物。如今把這個文件解析成 SIL,上面第二個文章也說了方法,這裏補充說明下吧。

swiftc -emit-silgen xxx.swift -Onone > xxxx.sil

struct TestStruct {
    func testFunc1() { print("ss") }
}

let t = TestStruct()
t.testFunc1()

複製代碼

以下圖所示,咱們要分析的是 main 中的代碼,經過搜尋函數名關鍵字能夠看到 %9 那裏是 function_ref,能夠看出這個是直接派發

如何看是不是派發方式:

  • function_ref:直接派發
  • class_method:虛函數表派發
  • witness_method:證據表派發(不知道咋翻譯了)
  • objc_method:消息機制派發

sil-struct.png

問題:

  1. 兩個文章都說 subclass.extension 會採用 消息機制派發,可是我實驗不出來這種狀況,以前他們有個例子是用在 extension 中 override ,可是如今這種寫法編譯器報錯了,不知道是否是由於這個
  2. 爲何函數表的方式沒法動態生成函數插入到表裏,消息派發機制能夠?消息派發機制如何動態添加到函數表的,暫時猜想 runtime 的函數表是多個表的集合,涉及到了 runtime 原理,暫時不太懂。
相關文章
相關標籤/搜索