Swift派發(fā)分:靜態(tài)派發(fā)和動(dòng)態(tài)派發(fā)
靜態(tài)派發(fā):(又叫:直接調(diào)用)
靜態(tài)派發(fā)機(jī)制层玲,同時(shí)支持值類(lèi)型和引用類(lèi)型抱冷;靜態(tài)派發(fā)是最快的, 不止是因?yàn)樾枰{(diào)用的指令集會(huì)更少, 并且編譯器還能夠有很大的優(yōu)化空間, 例如函數(shù)內(nèi)聯(lián)等,. 靜態(tài)派發(fā)也有人稱(chēng)為直接調(diào)用.
然而, 對(duì)于編程來(lái)說(shuō)直接調(diào)用也是最大的局限, 而且因?yàn)槿狈?dòng)態(tài)性所以沒(méi)辦法支持繼承
動(dòng)態(tài)派發(fā):
動(dòng)態(tài)派發(fā)機(jī)制僅支持引用類(lèi)型(reference types) 比如:Class如捅。簡(jiǎn)而言之:對(duì)于動(dòng)態(tài)性或者動(dòng)態(tài)派發(fā)袭祟,我們需要使用到繼承特性亏掀,而這是值類(lèi)型不支持的缸榄。動(dòng)態(tài)派發(fā)是調(diào)用函數(shù)最動(dòng)態(tài)的方式. 也是 Cocoa 的基石, 這樣的機(jī)制催生了 KVO, UIAppearence 和 CoreData 等功能. 這種運(yùn)作方式的關(guān)鍵在于開(kāi)發(fā)者可以在運(yùn)行時(shí)改變函數(shù)的行為. 不止可以通過(guò) swizzling 來(lái)改變, 甚至可以用 isa-swizzling 修改對(duì)象的繼承關(guān)系, 可以在面向?qū)ο蟮幕A(chǔ)上實(shí)現(xiàn)自定義派發(fā).
函數(shù)表派發(fā):
函數(shù)表派發(fā)是編譯型語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)行為最常見(jiàn)的實(shí)現(xiàn)方式. 函數(shù)表使用了一個(gè)數(shù)組來(lái)存儲(chǔ)類(lèi)聲明的每一個(gè)函數(shù)的指針. 大部分語(yǔ)言把這個(gè)稱(chēng)為 "virtual table"(虛函數(shù)表), Swift 里稱(chēng)為 "witness table". 每一個(gè)類(lèi)都會(huì)維護(hù)一個(gè)函數(shù)表, 里面記錄著類(lèi)所有的函數(shù), 如果父類(lèi)函數(shù)被 override 的話, 表里面只會(huì)保存被 override 之后的函數(shù). 一個(gè)子類(lèi)新添加的函數(shù), 都會(huì)被插入到這個(gè)數(shù)組的最后. 運(yùn)行時(shí)會(huì)根據(jù)這一個(gè)表去決定實(shí)際要被調(diào)用的函數(shù).
查表是一種簡(jiǎn)單, 易實(shí)現(xiàn), 而且性能可預(yù)知的方式. 然而, 這種派發(fā)方式比起靜態(tài)派發(fā)還是慢一點(diǎn). 從字節(jié)碼角度來(lái)看, 多了兩次讀和一次跳轉(zhuǎn), 由此帶來(lái)了性能的損耗. 另一個(gè)慢的原因在于編譯器可能會(huì)由于函數(shù)內(nèi)執(zhí)行的任務(wù)導(dǎo)致無(wú)法優(yōu)化. (如果函數(shù)帶有副作用的話)
這種基于數(shù)組的實(shí)現(xiàn), 缺陷在于函數(shù)表無(wú)法拓展. 子類(lèi)會(huì)在虛數(shù)函數(shù)表的最后插入新的函數(shù), 沒(méi)有位置可以讓 extension 安全地插入函數(shù). 這篇提案很詳細(xì)地描述了這么做的局限.
4種派發(fā)機(jī)制:
1渤弛、內(nèi)聯(lián)(inline)最快
2、靜態(tài)派發(fā)(Static Dispatch)
3甚带、函數(shù)表派發(fā)(Virtual Dispatch)
4暮芭、動(dòng)態(tài)派發(fā)(Dynamic Dispatch)(最慢)
由編譯器決定應(yīng)該使用哪種派發(fā)技術(shù)。當(dāng)然欲低,優(yōu)先選擇內(nèi)聯(lián)辕宏,然后按需選擇
靜態(tài)派發(fā)VS動(dòng)態(tài)派發(fā)(Swift VS OC)
OC默認(rèn)支持動(dòng)態(tài)派發(fā),這種派發(fā)形式以多態(tài)的形式為開(kāi)發(fā)人員提供了靈活性砾莱。
比如:子類(lèi)可以重寫(xiě)父類(lèi)的方法
動(dòng)態(tài)派發(fā)以一定的運(yùn)行時(shí)開(kāi)銷(xiāo)為代價(jià)瑞筐,提供了語(yǔ)言的靈活性。
在動(dòng)態(tài)派發(fā)機(jī)制下腊瑟,對(duì)于每個(gè)方法的調(diào)用聚假,編譯器必須在方法列表中查找執(zhí)行方法的實(shí)現(xiàn)。
編譯器需要判斷調(diào)用方闰非,是選擇父類(lèi)的實(shí)現(xiàn)還是子類(lèi)的實(shí)現(xiàn)膘格,而且由于所有對(duì)象的內(nèi)存都是在運(yùn)行時(shí)分配的,因此編譯器只能在運(yùn)行時(shí)執(zhí)行檢查财松。
而靜態(tài)調(diào)用則沒(méi)有這個(gè)問(wèn)題瘪贱。在編譯期的時(shí)候,編譯器就知道要為某個(gè)方法調(diào)用某種實(shí)現(xiàn)辆毡。因此編譯器可以執(zhí)行某些優(yōu)化菜秦,甚至在可能的情況下,可以將某些代碼轉(zhuǎn)換成inline函數(shù)舶掖,從而使整體執(zhí)行速度更快
如何在Swift中使用動(dòng)態(tài)派發(fā)和靜態(tài)派發(fā)球昨?
1、要實(shí)現(xiàn)動(dòng)態(tài)派發(fā)眨攘,可以使用繼承主慰,重寫(xiě)父類(lèi)的方法嚣州。另外我們可以使用dynamic關(guān)鍵字,并且需要在方法或類(lèi)前面加上關(guān)鍵字@objc共螺,以便方法公開(kāi)給OC runtime使用
2避诽、要實(shí)現(xiàn)靜態(tài)派發(fā),我們可以使用final和static關(guān)鍵字璃谨,保證不會(huì)被覆寫(xiě)
靜態(tài)派發(fā):
和動(dòng)態(tài)派發(fā)相比沙庐,非常快佳吞。編譯器可以在編譯器定位到函數(shù)的位置拱雏。因此函數(shù)被調(diào)用時(shí),編譯器能通過(guò)函數(shù)的內(nèi)存地址底扳,直接找到它的函數(shù)實(shí)現(xiàn)铸抑。極大的提高了性能,可以達(dá)到類(lèi)型inline的編譯期優(yōu)化
動(dòng)態(tài)派發(fā):
在這種類(lèi)型的派發(fā)中衷模,在運(yùn)行時(shí)而不是編譯時(shí)選擇實(shí)現(xiàn)方法鹊汛,會(huì)增加運(yùn)行時(shí)的性能開(kāi)銷(xiāo)。
優(yōu)勢(shì)是:具有靈活性(大多數(shù)的OOP語(yǔ)言都支持動(dòng)態(tài)派發(fā)阱冶,因?yàn)樗试S多態(tài))
動(dòng)態(tài)派發(fā)有兩種形式:
1刁憋、函數(shù)表派發(fā)(Table Dispatch)
這種調(diào)用方式利用一個(gè)表,該表是一組函數(shù)指針木蹬,稱(chēng)為witness table至耻,以查找特定方法的實(shí)現(xiàn)
witness table如何工作?
每個(gè)子類(lèi)都有它自己的表結(jié)構(gòu)
對(duì)于類(lèi)中每個(gè)重寫(xiě)的方法镊叁,都有不同的函數(shù)指針
當(dāng)子類(lèi)添加新方法時(shí)尘颓,這些方法指針會(huì)添加在表數(shù)組的末尾
最后,編譯器在運(yùn)行時(shí)使用此表來(lái)查找調(diào)用函數(shù)的實(shí)現(xiàn)
由于編譯器必須從表中讀取方法實(shí)現(xiàn)的內(nèi)存地址晦譬,然后跳轉(zhuǎn)到該地址疤苹,一次它需兩條附加指令,因此它比靜態(tài)派發(fā)慢敛腌,但仍比消息派發(fā)快
2卧土、消息派發(fā)(Message Dispatch)
這種動(dòng)態(tài)派發(fā)方式是最動(dòng)態(tài)的。事實(shí)上它表現(xiàn)優(yōu)異迎瞧,目前Cocoa框架在KVO夸溶,CoreData等很多地方在使用它
此外,它還可以使用method swizzling凶硅,可以在運(yùn)行時(shí)更改函數(shù)的實(shí)現(xiàn)。
Swift本身不支持消息派發(fā)扫皱,而是利用OC的runtime特性足绅,間接實(shí)現(xiàn)這種動(dòng)態(tài)性捷绑。
要使用動(dòng)態(tài)性需要使用dynamic關(guān)鍵字。Swift4.0之前氢妈,需要一起使用dynamic和@objc粹污。Swift4.0之后,只需表明@objc讓方法支持oc的調(diào)用首量,以支持消息派發(fā)
具體通過(guò)如下代碼來(lái)深入體會(huì):
/*
值類(lèi)型:
由于struct和enum都是值類(lèi)型壮吩,不支持繼承,編譯器將它們置為靜態(tài)派發(fā)下加缘,因?yàn)樗鼈冇肋h(yuǎn)不可能被子類(lèi)化
*/
struct LTPerson {
? ? //靜態(tài)派發(fā)
? ? func chinesePerson() -> Bool {
? ? ? ? return true
? ? }
}
extension LTPerson {
? ? ////靜態(tài)派發(fā)
? ? func canDispatch() -> Bool {
? ? ? ? return true
? ? }
}
/*
協(xié)議
這里重點(diǎn)是在extension(擴(kuò)展)里面定義的函數(shù)鸭叙,使用靜態(tài)派發(fā)(static dispatch)
*/
protocol LTAnimal{
? ? //函數(shù)表派發(fā)
? ? func isAnimal() -> Bool
}
extension LTAnimal{
? ? //靜態(tài)派發(fā)
? ? func canDispatch() -> Bool{
? ? ? ? return true
? ? }
}
/*
類(lèi)
普通方法聲明遵循協(xié)議的規(guī)則
當(dāng)將方法公開(kāi)給OC runtime是使用@objc,使用動(dòng)態(tài)派發(fā)
當(dāng)一個(gè)類(lèi)表標(biāo)記為final是拣宏,該類(lèi)不能被子類(lèi)化沈贝,使用靜態(tài)派發(fā)
*/
class LTCat: LTAnimal{
? ? //函數(shù)表派發(fā)
? ? func isAnimal() -> Bool {
? ? ? ? return true
? ? }
? ? //動(dòng)態(tài)派發(fā)
? ? @objc dynamic func hoursSleep() -> Int{
? ? ? ? return12
? ? }
}
extension LTCat{
? ? //靜態(tài)派發(fā)
? ? func canSwim() -> Bool{
? ? ? ? return false
? ? }
? ? //動(dòng)態(tài)派發(fā)
? ? @objc func goWild(){
? ? }
}
final class LTEmployee{
? ? //靜態(tài)派發(fā)
? ? func canSpeak()->Bool{
? ? ? ? return true
? ? }
}
通過(guò)上面的代碼可以知道是使用的哪種派發(fā)方式;具體可以通過(guò)sil文件來(lái)進(jìn)行驗(yàn)證
生成SIL文件命令:swiftc -emit-sil main.swift >> main.sil
如果使用table派發(fā)勋乾,則會(huì)出現(xiàn)在value(或者witness_table)中
如果使用動(dòng)態(tài)轉(zhuǎn)發(fā)宋下,則能找到objc_method標(biāo)記,指示使用OC運(yùn)行時(shí)調(diào)用了該函數(shù)
如果沒(méi)有出現(xiàn)以上兩種情況的標(biāo)記辑莫,則是靜態(tài)派發(fā)