Swift 函數(shù)派發(fā)機制

函數(shù)派發(fā)方式

能夠在編譯期確定執(zhí)行方法的方式叫做靜態(tài)分派 Static dispatch蒜危,無法在編譯期確定蹦渣,只能在運行時去確定執(zhí)行方法的分派方式叫做動態(tài)分派 Dynamic dispatch内狸。

靜態(tài)分派(Static dispatch)

Static dispatch 更快夷恍,CPU 直接拿到函數(shù)地址并進行調(diào)用蚓峦,而且靜態(tài)分派可以進行內(nèi)聯(lián)等進一步的優(yōu)化卵迂,使得執(zhí)行更快速察蹲,性能更高请垛。

使用 Static dispatch 代替 Dynamic dispatch 提升性能

我們知道Static dispatch快于Dynamic dispatch,如何在開發(fā)中去盡可能使用Static dispatch洽议。

  • inheritance constraints繼承約束

    我們可以使用 final 關(guān)鍵字去修飾 Class宗收,以此生成的 Final class,使用 Static dispatch亚兄。

  • access control訪問控制

    private關(guān)鍵字修飾混稽,使得方法或?qū)傩灾粚Ξ?dāng)前類可見。編譯器會對方法進行 Static dispatch

編譯器可以通過 whole module optimization 檢查繼承關(guān)系匈勋,對某些沒有標(biāo)記 final 的類通過計算礼旅,如果能在編譯期確定執(zhí)行的方法,則使用 Static dispatch颓影。 Struct 默認使用 Static dispatch各淀。

Swift 提供了更靈活的Struct,用以在內(nèi)存诡挂、引用計數(shù)碎浇、方法分派等角度去進行性能的優(yōu)化,在正確的時機選擇正確的數(shù)據(jù)結(jié)構(gòu)璃俗,可以使我們的代碼性能更快更安全奴璃。

如果你正在面試,或者正準(zhǔn)備跳槽城豁,不妨看看我精心總結(jié)的面試資料https://gitee.com/Mcci7/i-oser 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障

你可能會問 Struct 如何實現(xiàn)多態(tài)呢?
答案是 protocol oriented programming苟穆。

以上分析了影響性能的幾個標(biāo)準(zhǔn),那么不同的算法機制Class唱星,Protocol TypesGeneric code雳旅,它們在這三方面的表現(xiàn)如何,Protocol TypeGeneric code 分別是怎么實現(xiàn)的呢间聊?我們帶著這個問題看下去攒盈。

Protocol Type

這里我們會討論Protocol Type如何存儲和拷貝變量,以及方法分派是如何實現(xiàn)的哎榴。不通過繼承或者引用語義的多態(tài):

protocol Drawable { 
    func draw() 
}
struct Point :Drawable {
    var x, y:Double 
    func draw() { … } 
} 
struct Line :Drawable { 
    var x1, y1, x2, y2:Double 
    func draw() { 
    … 
    } 
} 

var drawables:[Drawable] //遵守了Drawable協(xié)議的類型集合型豁,可能是point或者line 
for d in drawables { 
    d.draw() 
}
復(fù)制代碼

因為 PointLine 的尺寸不同,數(shù)組存儲數(shù)據(jù)實現(xiàn)一致性存儲尚蝌,使用了Existential Container迎变。查找正確的執(zhí)行方法則使用了 Protoloc Witness Table

以上通過 Protocol Type 實現(xiàn)多態(tài)飘言,幾個類之間沒有繼承關(guān)系衣形,故不能按照慣例借助 V-Table 實現(xiàn)動態(tài)分派。但是對于 swift 來說姿鸿,class 類和 struct 結(jié)構(gòu)體的實現(xiàn)是不同的,而屬于結(jié)構(gòu)體的協(xié)議Protocol谆吴,可以擁有屬性和實現(xiàn)方法,管理Protocol Type方法分派的表就叫做Protocol Witness Table般妙。

Protocol Witness Table

V-table 一樣,Protocol Witness Table(簡稱 PWT )內(nèi)存儲的是方法數(shù)組,里面包含了方法實現(xiàn)的指針地址,一般我們調(diào)用方法時,是通過獲取對象的內(nèi)存地址和方法的位移offset去查找的.

Protocol Witness Table 是用于管理 Protocol Type 的方法調(diào)用的,在我們接觸 swift 性能優(yōu)化時,聽到另一個概念叫做 Value Witness Table (簡稱 VWT),這個又是做什么的呢?

Value Witness Table

[圖片上傳失敗...(image-1b0d39-1638867090452)]

用于管理任意值的初始化、拷貝相速、銷毀碟渺。即對 Protocol Type 的生命周期進行專項管理

對于每一個類型(Int或者自定義),都在metadata中存儲了一個VWT(用來管理當(dāng)前類型的值)

Value Witness TableProtocol Witness Table 通過分工,去管理 Protocol Type 實例的內(nèi)存管理(初始化苫拍,拷貝芜繁,銷毀)和方法調(diào)用。

動態(tài)分派

但是對于多態(tài)的情況绒极,我們不能在編譯期確定最終的類型骏令,這里就用到了 Dynamic dispatch 動態(tài)分派。動態(tài)分派的實現(xiàn)是垄提,每種類型都會創(chuàng)建一張表榔袋,表內(nèi)是一個包含了方法指針的數(shù)組。動態(tài)分派更靈活铡俐,但是因為有查表和跳轉(zhuǎn)的操作凰兑,并且因為很多特點對于編譯器來說并不明確,所以相當(dāng)于 block 了編譯器的一些后期優(yōu)化审丘。所以速度慢于 Static dispatch吏够。

函數(shù)表派發(fā)(Table dispatch)

編譯型語言中最常見的派發(fā)方式,既保證了動態(tài)性也兼顧了執(zhí)行效率滩报。

函數(shù)所在的類會維護一個“函數(shù)表”(虛函數(shù)表)锅知,存取了每個函數(shù)實現(xiàn)的指針。

每個類的 vtable 在編譯時就會被構(gòu)建脓钾,所以與靜態(tài)派發(fā)相比多出了兩個讀取的工作:

  • 讀取該類的 vtable
  • 讀取函數(shù)的指針

優(yōu)點:

  • 查表是一種簡單售睹,易實現(xiàn),而且性能可預(yù)知的方式惭笑。
  • 理論上說侣姆,函數(shù)表派發(fā)也是一種高效的方式。

缺點:

  • 與靜態(tài)派發(fā)相比沉噩,從字節(jié)碼角度來看捺宗,多了兩次讀和一次跳轉(zhuǎn)。
  • 與靜態(tài)派發(fā)相比川蒙,編譯器對某些含有副作用的函數(shù)無法優(yōu)化笔咽。
  • Swift 類擴展里面的函數(shù)無法動態(tài)加入該類的函數(shù)表中,只能使用靜態(tài)派發(fā)的方式旋恼。

舉個例子(只是一個示例):

class A {
    func method1() {}
}
class B: A {
    func method2() {}
}
class C: B {
    override func method2() {}
    func method3() {}
}
復(fù)制代碼

復(fù)制代碼
offset 0xA00 A 0xB00 B 0xC00 C
0 0x121 A.method1 0x121 A.method1 0x121 A.method1
1 0x222 B.method2 0x322 C.method2
2 0x323 C.method3
let obj = C()
obj.method2()
復(fù)制代碼

當(dāng)method2被調(diào)用時泛鸟,會經(jīng)歷下面的幾個過程:

  1. 讀取對象 0xC00 的函數(shù)表
  2. 讀取函數(shù)指針的索引, method2 的地址為0x322
  3. 跳轉(zhuǎn)執(zhí)行 0x322

消息派發(fā)(Message dispatch)

熟悉 OC 的人都知道康聂,OC 采用了運行時機制使用 obj_msgSend 發(fā)送消息贰健,runtime 非常的靈活,我們不僅可以對方法調(diào)用采用 swizzling恬汁,對于對象也可以通過 isa-swizzling 來擴展功能伶椿,應(yīng)用場景有我們常用的 hook 和大家熟知的 KVO

大家在使用 Swift 進行開發(fā)時都會問,Swift 是否可以使用OC的運行時和消息轉(zhuǎn)發(fā)機制呢脊另?答案是可以导狡。

Swift 可以通過關(guān)鍵字 dynamic 對方法進行標(biāo)記,這樣就會告訴編譯器偎痛,此方法使用的是 OC 的運行時機制旱捧。

id returnValue = [obj messageName:param];
// 底層代碼
id returnValue = objc_msgSend(obj, @selector(messageName:), param);
復(fù)制代碼
復(fù)制代碼

優(yōu)點:

  • 動態(tài)性高
  • Method Swizzling
  • isa Swizzling
  • ...

缺點:

  • 執(zhí)行效率是三種派發(fā)方式中最低的

所幸的是 objc_msgSend 會將匹配的結(jié)果緩存到一個映射表中,每個類都有這樣一塊緩存踩麦。若是之后發(fā)送相同的消息枚赡,執(zhí)行速率會很快。

總結(jié)來說靖榕,Swift 通過 dynamic 關(guān)鍵字的擴展后标锄,一共包含三種方法分派方式:Static dispatchTable dispatchMessage dispatch茁计。下表為不同的數(shù)據(jù)結(jié)構(gòu)在不同情況下采取的分派方式:

類型 靜態(tài)派發(fā) 函數(shù)表派發(fā) 消息派發(fā)
值類型 所有方法 / /
協(xié)議 extension 主體創(chuàng)建 /
extension/final/static 主體創(chuàng)建 @objc + dynamic
NSObject子類 extension/final/static 主體創(chuàng)建 @objc + dynamic

如果在開發(fā)過程中料皇,錯誤的混合了這幾種分派方式,就可能出現(xiàn) Bug星压,以下我們對這些 Bug 進行分析:

此情況是在子類的 extension 中重載父類方法時践剂,出現(xiàn)和預(yù)期不同的行為。

class Base:NSObject {
    var directProperty:String { return "This is Base" }
    var indirectProperty:String { return directProperty }
}

class Sub:Base { }

extension Sub {
    override var directProperty:String { return "This is Sub" }
}
復(fù)制代碼

執(zhí)行以下代碼娜膘,直接調(diào)用沒有問題:

Base().directProperty // “This is Base”
Sub().directProperty // “This is Sub”
復(fù)制代碼

間接調(diào)用結(jié)果和預(yù)期不同:

Base().indirectProperty // “This is Base”
Sub().indirectProperty // expected "this is Sub"逊脯,but is “This is Base” <- Unexpected!
復(fù)制代碼

Base.directProperty 前添加 dynamic 關(guān)鍵字就可以獲得 "this is Sub" 的結(jié)果。Swiftextension 文檔 中說明竣贪,不能在 extension 中重載已經(jīng)存在的方法军洼。

“Extensions can add new functionality to a type, but they cannot override existing functionality.”

會出現(xiàn)報錯:Cannot override a non-dynamic class declaration from an extension

[圖片上傳失敗...(image-c05dcd-1638867090451)]

出現(xiàn)這個問題的原因是演怎,NSObjectextension 是使用的 Message dispatch匕争,而 Initial Declaration 使用的是 Table dispath(查看上圖)。extension 重載的方法添加在了 Message dispatch 內(nèi)爷耀,沒有修改虛函數(shù)表甘桑,虛函數(shù)表內(nèi)還是父類的方法,故會執(zhí)行父類方法歹叮。想在 extension 重載方法跑杭,需要標(biāo)明dynamic來使用 Message dispatch

協(xié)議的擴展內(nèi)實現(xiàn)的方法咆耿,無法被遵守類的子類重載:

protocol Greetable {
    func sayHi()
}
extension Greetable {
    func sayHi() {
        print("Hello")
    }
}
func greetings(greeter:Greetable) {
    greeter.sayHi()
}
復(fù)制代碼

現(xiàn)在定義一個遵守了協(xié)議的類 Person德谅。遵守協(xié)議類的子類 LoudPerson

class Person:Greetable {
}
class LoudPerson:Person {
    func sayHi() {
        print("sub")
    }
}
復(fù)制代碼

執(zhí)行下面代碼結(jié)果為:

var sub:LoudPerson = LoudPerson()
sub.sayHi()  //sub
復(fù)制代碼

不符合預(yù)期的代碼:

var sub:Person = LoudPerson()
sub.sayHi()  //HellO  <-使用了protocol的默認實現(xiàn)
復(fù)制代碼

注意,在子類 LoudPerson 中沒有出現(xiàn) override 關(guān)鍵字萨螺≌觯可以理解為 LoudPerson 并沒有成功注冊 GreetableWitness table 的方法宅荤。所以對于聲明為 Person 實際為 LoudPerson 的實例,會在編譯器通過 Person 去查找浸策,Person 沒有實現(xiàn)協(xié)議方法,則不產(chǎn)生 Witness table惹盼,sayHi 方法是直接調(diào)用的庸汗。解決辦法是在 base類 內(nèi)實現(xiàn)協(xié)議方法,無需實現(xiàn)也要提供默認方法手报◎遣眨或者將基類標(biāo)記為 final 來避免繼承

這里的確沒有編譯驗證掩蛤,感謝 MemoryReload 的實踐驗證枉昏,自己編譯了源碼之后糾正一下,這里的直接調(diào)用的確不是上述的原因:

  • 在子類 LoudPerson 中實現(xiàn)了 sayHi 方法揍鸟,所以會直接調(diào)用 LoudPersonsayHi 方法兄裂。
  • 對于聲明為 Person 實際為 LoudPerson 的實例,會在編譯器通過 Person 去查找阳藻,聲明在協(xié)議中的函數(shù)是使用函數(shù)表 vtable 派發(fā)的晰奖,從下面編譯的 SIL 代碼中也可以看到,Person 的函數(shù)表 vtable 中沒有 sayHi 方法腥泥,實際上匾南,Person 類的 sayHi 方法只是在接口擴展中進行了定義,沒有最終的類型中實現(xiàn)蛔外。在使用時蛆楞,因為 Person 類只是一個符合 Greetable 接口的實例,編譯器對 sayHi 唯一能確定的只是在接口擴展中有一個默認實現(xiàn)豹爹,因此在調(diào)用時尊流,無法確定安全,也就不會去進行動態(tài)派發(fā)崖技,而是轉(zhuǎn)而編譯期間就確定的默認實現(xiàn)逻住。
import Foundation

protocol Greetable {
  func sayHi()
}

extension Greetable {
  func sayHi()
}

func greetings(greeter: Greetable)

class Person : Greetable {
  @objc deinit
  init()
}

@_inheritsConvenienceInitializers class LoudPerson : Person {
  func sayHi()
  override init()
  @objc deinit
}

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
...
} // end sil function 'main'

// Greetable.sayHi()
sil hidden @$s16FunctionDispatch9GreetablePAAE5sayHiyyF : $@convention(method) <Self where Self : Greetable> (@in_guaranteed Self) -> () {
// %0 "self"                                      // user: %1
bb0(%0 : $*Self):
  debug_value_addr %0 : $*Self, let, name "self", argno 1 // id: %1
  %2 = integer_literal $Builtin.Word, 1           // user: %4
  // function_ref _allocateUninitializedArray<A>(_:)
  %3 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
  %4 = apply %3<Any>(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
  %5 = tuple_extract %4 : $(Array<Any>, Builtin.RawPointer), 0 // user: %17
  %6 = tuple_extract %4 : $(Array<Any>, Builtin.RawPointer), 1 // user: %7
  %7 = pointer_to_address %6 : $Builtin.RawPointer to [strict] $*Any // user: %14
  %8 = string_literal utf8 "Hello"                // user: %13
  %9 = integer_literal $Builtin.Word, 5           // user: %13
  %10 = integer_literal $Builtin.Int1, -1         // user: %13
  %11 = metatype $@thin String.Type               // user: %13
  ...
} // end sil function '$s16FunctionDispatch9GreetablePAAE5sayHiyyF'

// protocol witness for Greetable.sayHi() in conformance Person
sil private [transparent] [thunk] @$s16FunctionDispatch6PersonCAA9GreetableA2aDP5sayHiyyFTW : $@convention(witness_method: Greetable) <τ_0_0 where τ_0_0 : Person> (@in_guaranteed τ_0_0) -> () {
// %0                                             // user: %2
bb0(%0 : $*τ_0_0):
  // function_ref Greetable.sayHi()
  %1 = function_ref @$s16FunctionDispatch9GreetablePAAE5sayHiyyF : $@convention(method) <τ_0_0 where τ_0_0 : Greetable> (@in_guaranteed τ_0_0) -> () // user: %2
  %2 = apply %1<τ_0_0>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : Greetable> (@in_guaranteed τ_0_0) -> ()
  %3 = tuple ()                                   // user: %4
  return %3 : $()                                 // id: %4
} // end sil function '$s16FunctionDispatch6PersonCAA9GreetableA2aDP5sayHiyyFTW'

// LoudPerson.sayHi()
sil hidden @$s16FunctionDispatch10LoudPersonC5sayHiyyF : $@convention(method) (@guaranteed LoudPerson) -> () {

sil_vtable Person {
  #Person.init!allocator: (Person.Type) -> () -> Person : @$s16FunctionDispatch6PersonCACycfC   // Person.__allocating_init()
  #Person.deinit!deallocator: @$s16FunctionDispatch6PersonCfD   // Person.__deallocating_deinit
}

sil_vtable LoudPerson {
  #Person.init!allocator: (Person.Type) -> () -> Person : @$s16FunctionDispatch10LoudPersonCACycfC [override]   // LoudPerson.__allocating_init()
  #LoudPerson.sayHi: (LoudPerson) -> () -> () : @$s16FunctionDispatch10LoudPersonC5sayHiyyF // LoudPerson.sayHi()
  #LoudPerson.deinit!deallocator: @$s16FunctionDispatch10LoudPersonCfD  // LoudPerson.__deallocating_deinit
}

sil_witness_table hidden Person: Greetable module FunctionDispatch {
  method #Greetable.sayHi: <Self where Self : Greetable> (Self) -> () -> () : @$s16FunctionDispatch6PersonCAA9GreetableA2aDP5sayHiyyFTW // protocol witness for Greetable.sayHi() in conformance Person
}
復(fù)制代碼

進一步通過示例去理解:

// Defined protocol瞎访。
protocol A {
    func a() -> Int
}
extension A {
    func a() -> Int {
        return 0
    }
}

// A class doesn't have implement of the function。
class B:A {}

class C:B {
    func a() -> Int {
        return 1
    }
}

// A class has implement of the function吁恍。
class D:A {
    func a() -> Int {
        return 1
    }
}

class E:D {
    override func a() -> Int {
        return 2
    }
}

// Failure cases播演。
B().a() // 0
C().a() // 1
(C() as A).a() // 0 # We thought return 1伴奥。 

// Success cases拾徙。
D().a() // 1
(D() as A).a() // 1
E().a() // 2
(E() as A).a() // 2
復(fù)制代碼

如果對上述代碼的執(zhí)行結(jié)果理解的不到位的話,還可以借助喵神 PROTOCOL EXTENSION 里面的例子理解一下:

現(xiàn)在我們可以對一個已有的 protocol 進行擴展暂衡,而擴展中實現(xiàn)的方法將作為實現(xiàn)擴展的類型的默認實現(xiàn)崖瞭。也就是說,假設(shè)我們有下面的 protocol 聲明唧领,以及一個對該接口的擴展:

protocol MyProtocol {
    func method()
}

extension MyProtocol {
    func method() {
        print("Called")
    }
}
復(fù)制代碼

在具體的實現(xiàn)這個接口的類型中疹吃,即使我們什么都不寫西雀,也可以編譯通過。進行調(diào)用的話腔呜,會直接使用 extension 中的實現(xiàn):

struct MyStruct: MyProtocol {

}

MyStruct().method()
// 輸出:
// Called in extension
復(fù)制代碼

當(dāng)然再悼,如果我們需要在類型中進行其他實現(xiàn)的話冲九,可以像以前那樣在具體類型中添加這個方法:

struct MyStruct: MyProtocol {
    func method() {
        print("Called in struct")
    }
}

MyStruct().method()
// 輸出:
// Called in struct
復(fù)制代碼

也就是說,protocol extension 為 protocol 中定義的方法提供了一個默認的實現(xiàn)丑孩。有了這個特性以后温学,之前被放在全局環(huán)境中的接受 CollectionTypemap 方法甚疟,就可以被移動到 CollectionType 的接口擴展中去了:

extension CollectionType {
    public func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
    //...
}
復(fù)制代碼

在日常開發(fā)中,另一個可以用到 protocol extension 的地方是 optional 的接口方法揽祥。通過提供 protocol 的 extension盔然,我們?yōu)?protocol 提供了默認實現(xiàn)涂召,這相當(dāng)于變相將 protocol 中的方法設(shè)定為了 optional。關(guān)于這個炎码,我們在可選接口和接口擴展一節(jié)中已經(jīng)講述過潦闲,就不再重復(fù)了迫皱。

對于 protocol extension 來說,有一種會非常讓人迷惑的情況和敬,就是在接口的擴展中實現(xiàn)了接口里沒有定義的方法時的情況戏阅。舉個例子饲握,比如我們定義了這樣的一個接口和它的一個擴展:

protocol A1 {
    func method1() -> String
}

struct B1: A1 {
    func method1() -> String {
        return "hello"
    }
}
復(fù)制代碼

在使用的時候蚕键,無論我們將實例的類型為 A1 還是 B1锣光,因為實現(xiàn)只有一個铝耻,所以沒有任何疑問瓢捉,調(diào)用方法時的輸出都是 “hello”:

let b1 = B1() // b1 is B1
b1.method1()
// hello

let a1: A1 = B1()
// a1 is A1
a1.method1()
// hello
復(fù)制代碼

但是如果在接口里只定義了一個方法,而在接口擴展中實現(xiàn)了額外的方法的話搂漠,事情就變得有趣起來了某弦“凶常考慮下面這組接口和它的擴展:

protocol A2 {
    func method1() -> String
}

extension A2 {
    func method1() -> String {
        return "hi"
    }

    func method2() -> String {
        return "hi"
    }
}
復(fù)制代碼

擴展中除了實現(xiàn)接口定義的 method1 之外,還定義了一個接口中不存在的方法 method2拣度。我們嘗試來實現(xiàn)這個接口:

struct B2: A2 {
    func method1() -> String {
        return "hello"
    }

    func method2() -> String {
        return "hello"
    }
}
復(fù)制代碼

B2 中實現(xiàn)了 method1method2蜡娶。接下來映穗,我們嘗試初始化一個 B2 對象,然后對這兩個方法進行調(diào)用:

let b2 = B2()

b2.method1() // hello
b2.method2() // hello
復(fù)制代碼

結(jié)果在我們的意料之中宿接,雖然在 protocol extension 中已經(jīng)實現(xiàn)了這兩個方法睦霎,但是它們只是默認的實現(xiàn)走诞,我們在具體實現(xiàn)接口的類型中可以對默認實現(xiàn)進行覆蓋蚣旱,這非常合理戴陡。但是如果我們稍作改變沟涨,在上面的代碼后面繼續(xù)添加:

let a2 = b2 as A2

a2.method1() // hello
a2.method2() // hi
復(fù)制代碼

a2b2 是同一個對象裹赴,只不過我們通過 as 告訴編譯器我們在這里需要的類型是 A2。但是這時候在這個同樣的對象上調(diào)用同樣的方法調(diào)用卻得到了不同的結(jié)果延都,發(fā)生了什么晰房?

我們可以看到酵颁,對 a2 調(diào)用 method2 實際上是接口擴展中的方法被調(diào)用了月帝,而不是 a2 實例中的方法被調(diào)用嚷辅。我們不妨這樣來理解:對于 method1,因為它在 protocol 中被定義了扁位,因此對于一個被聲明為遵守接口的類型的實例 (也就是對于 a2) 來說域仇,可以確定實例必然實現(xiàn)了 method1寺擂,我們可以放心大膽地用動態(tài)派發(fā)的方式使用最終的實現(xiàn) (不論它是在類型中的具體實現(xiàn)怔软,還是在接口擴展中的默認實現(xiàn));但是對于 method2 來說括改,我們只是在接口擴展中進行了定義家坎,沒有任何規(guī)定說它必須在最終的類型中被實現(xiàn)。在使用時憔涉,因為 a2 只是一個符合 A2 接口的實例析苫,編譯器對 method2 唯一能確定的只是在接口擴展中有一個默認實現(xiàn)衩侥,因此在調(diào)用時,無法確定安全跪但,也就不會去進行動態(tài)派發(fā)峦萎,而是轉(zhuǎn)而編譯期間就確定的默認實現(xiàn)爱榔。

也許在這個例子中你會覺得無所謂,因為實際中估計并不會有人將一個已知類型實例轉(zhuǎn)回接口類型筛欢。但是要考慮到如果你的一些泛型 API 中有類似的直接拿到一個接口類型的結(jié)果的時候唇聘,調(diào)用它的擴展方法時就需要特別小心了:一般來說迟郎,如果有這樣的需求的話宪肖,我們可以考慮將這個接口類型再轉(zhuǎn)回實際的類型,然后進行調(diào)用夫凸。

整理一下相關(guān)的規(guī)則的話:

  • 如果類型推斷得到的是實際的類型

    • 那么類型中的實現(xiàn)將被調(diào)用阱持;如果類型中沒有實現(xiàn)的話,那么接口擴展中的默認實現(xiàn)將被使用
  • 如果類型推斷得到的是接口蒜绽,而不是實際類型

    • 并且方法在接口中進行了定義桶现,那么類型中的實現(xiàn)將被調(diào)用骡和;如果類型中沒有實現(xiàn),那么接口擴展中的默認實現(xiàn)被使用

    • 否則 (也就是方法沒有在接口中定義)钮科,擴展中的默認實現(xiàn)將被調(diào)用

參考:

PROTOCOL EXTENSION

【基本功】深入剖析Swift性能優(yōu)化

從SIL看Swift函數(shù)派發(fā)機制

作者:奉孝
鏈接:https://juejin.cn/post/7033682844581019656

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绵脯,一起剝皮案震驚了整個濱河市蛆挫,隨后出現(xiàn)的幾起案子妙黍,更是在濱河造成了極大的恐慌废境,老刑警劉巖筒繁,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毡咏,死亡現(xiàn)場離奇詭異呕缭,居然都是意外死亡,警方通過查閱死者的電腦和手機迎罗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門纹安,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厢岂,“玉大人,你說我怎么就攤上這事结借∽洳纾” “怎么了扬虚?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵辜昵,是天一觀的道長堪置。 經(jīng)常有香客問我,道長岭洲,這世上最難降的妖魔是什么坎匿? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任替蔬,我火速辦了婚禮承桥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜀撑。我一直安慰自己酷麦,他們只是感情好喉恋,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著徘意,像睡著了一般椎咧。 火紅的嫁衣襯著肌膚如雪把介。 梳的紋絲不亂的頭發(fā)上拗踢,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天巢墅,我揣著相機與錄音,去河邊找鬼驯遇。 笑死叉庐,一個胖子當(dāng)著我的面吹牛会喝,可吹牛的內(nèi)容都是我干的肢执。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼临庇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了淮蜈?” 一聲冷哼從身側(cè)響起梧田,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹉梨,沒想到半個月后存皂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逢艘,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡它改,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年祭阀,在試婚紗的時候發(fā)現(xiàn)自己被綠了鲜戒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袍啡。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡境输,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辩越,到底是詐尸還是另有隱情黔攒,我是刑警寧澤强缘,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布旅掂,位于F島的核電站商虐,受9級特大地震影響崖疤,放射性物質(zhì)發(fā)生泄漏劫哼。R本人自食惡果不足惜割笙,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一咳蔚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侈询,春花似錦扔字、人聲如沸温技。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迂猴,卻和暖如春沸毁,著一層夾襖步出監(jiān)牢的瞬間傻寂,已是汗流浹背崎逃。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工个绍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巴柿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓凯旋,卻偏偏與公主長得像至非,于是被迫代替她去往敵國和親荒椭。 傳聞我的和親對象是個殘疾皇子舰蟆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容