親俏脊,我的簡書已不再維護(hù)和更新了戒幔,所有文章都遷移到了我的個人博客:https://mikefighting.github.io/拌喉,歡迎交流派诬。
Swift相比OC以及其它語言盟萨,有很多的優(yōu)化點吝羞,這篇文章將從方法調(diào)度的角度去說明為什么Swift要比OC更快兰伤。OC是一門動態(tài)的語言,很多實際執(zhí)行需要在運行時才可以確定钧排,Swift不一樣敦腔,Swift將很多在運行時才可以確定的信息,在編譯期就決定了恨溜。這就讓Swift更加快速符衔。
方法調(diào)度就是程序在觸發(fā)方法時選擇需要執(zhí)行指令的過程,它在每次方法執(zhí)行時都會發(fā)生糟袁。如果這種調(diào)度發(fā)生在編譯期判族,我們稱它為靜態(tài)調(diào)度(Static Dispatch),如果調(diào)度發(fā)生在運行時项戴,那么我們稱它為動態(tài)調(diào)度(Dynamic Dispatch)形帮。靜態(tài)調(diào)度往往要比動態(tài)調(diào)度要快。那么問題來了,為什么我們需要動態(tài)調(diào)度呢辩撑?全部用靜態(tài)調(diào)度不就得了界斜?
問題就在于我們很多時候我們需要用到多態(tài),看看下面這段非常簡單的代碼
class Animal {
func eat() {
print("animal eat");
}
func sleep() {
print("animal sleep")
}
}
class Dog: Animal {
override func sleep() {
print("dog sleep")
}
}
class Rabbit: Animal {
override func eat() {
print("rabbit sleep");
}
override func sleep() {
print("rabbit sleep")
}
}
var animal:Animal?
var somThingTrue = false
//執(zhí)行很多業(yè)務(wù)邏輯
if somThingTrue {
animal = Rabbit()
}else{
animal = Dog()
}
animal?.eat()
上面的代碼中animal?.eat()
就不能夠在編譯期確定合冀,因為其中需要很多的業(yè)務(wù)邏輯(比如根據(jù)用戶的不同各薇,或者網(wǎng)絡(luò)請求結(jié)果的不同)來確定就究竟創(chuàng)建出來的對象是Rabbit還是Dog,也就無法最終確定要調(diào)用那個對象的eat()方法君躺。相似的代碼在OC中是怎樣執(zhí)行的呢峭判?在OC中編譯器會將這個方法翻譯成objc_msgSend(target,@selector(eat),nil)
這個方法,然后到了運行時棕叫,會分為以下幾步進(jìn)行調(diào)用:
- 找到方法target中isa對應(yīng)的Class(如果是類方法要到其metaClass中找)朝抖。
- 從其中的
struct objc_method_list **methodLists
找到對應(yīng)的方法實現(xiàn)。 - 如果沒有找到就到superClass的
methodLists
中找谍珊。
如果在Swift中治宣,它是怎樣做方法調(diào)度的呢?
找到target對應(yīng)的class
-
從class的V-Table中的那得到函數(shù)的實現(xiàn)
Swift中的類會創(chuàng)建一個V-Table砌滞,這個Table是一個數(shù)組侮邀,其中存放的是函數(shù)指針。子類會按照父類V-Table中函數(shù)的存放贝润,如果子類沒有覆蓋某個方法绊茧,那么就會拷貝父類方法的地址,如上面的例子會得到下面的V-Table打掘。Animal
Index0 eat 0x0001
Index1 sleep 0x0004Dog
Index0 eat 0x0001 (copied)
Index1 sleep 0x0008 (overrideen)Rabbit
Index0 eat 0x0002 (overrideen)
Index1 sleep 0x0003 (overrideen)
可以注意到Dog因為沒有覆蓋父類的eat
方法华畏,所以其copy了父類的0x0010
指針。因為Swift是Type Safe的尊蚁,所以在調(diào)用它的時候它不會變成Robot
或者其它的類(如果不能通過編譯)亡笑,所以無論是調(diào)用上面結(jié)構(gòu)中的Animal,Dog横朋,還是Rabbit類仑乌,它都是調(diào)用相同的Index,得到對應(yīng)的方法實現(xiàn)琴锭。將函數(shù)指針和Index所做的映射在編譯期就確定了晰甚,這就大大減少了運行時的工作量,提高了運行速度决帖。所以在運行時它沒有必要知道是哪個類型的實例調(diào)用了這個方法厕九,只需要找到相應(yīng)的V-Table即可,至于是其中的哪個Index已經(jīng)在編譯期確定了地回,沒必要再去查找Index的值扁远。
然而Swift的方法調(diào)度不僅僅是動態(tài)方法調(diào)度腺阳,還有很多靜態(tài)方法調(diào)度。
如果我們將某個方法標(biāo)記為final或者private穿香,或者我們不用類亭引,而使用結(jié)構(gòu)體,枚舉皮获,這時就不需要動態(tài)調(diào)度焙蚓,只需要靜態(tài)調(diào)度即可,這樣速度會更快洒宝。