14年Swift推出時的主打口號就是“快”。這也十分高調(diào)地被體現(xiàn)在這門語言的名字上了。其實快字主要體現(xiàn)在編譯器運行時系統(tǒng)。新的Swift編譯器更智能,能夠識別對象和方法的調(diào)用關(guān)系以及層級關(guān)系,減少對象調(diào)用方法的查找時間;同時在內(nèi)存管理上也有所提高。首先簡要回顧一下ObjC runtime的原理,ObjC使用Messaging策略——選擇器向接收器發(fā)送消息,編譯階段無法得知接收器對象或類是否有對應的方法,僅將自定義的面向?qū)ο箢愋途幾g生成對應的運行時類型,即大量C結(jié)構(gòu)體和C函數(shù)。程序運行時階段,運行時系統(tǒng)根據(jù)對象的isa指針找到對象所屬的類結(jié)構(gòu)體,然后通過結(jié)合類中的緩存方法列表指針和虛函數(shù)表指針進行查找選擇器對應的SEL選擇器類型變量,如果找到則根據(jù)SEL變量對應的IMP指針找到方法實現(xiàn)。若找不到對應的方法,則會啟動消息轉(zhuǎn)發(fā)機制,如果仍然失敗,則拋出運行時異常。這個Messaging的過程是ObjC runtime消耗資源的一大因素。而大部分的方法調(diào)用,尤其是重復的方法調(diào)用,并沒有必要次次都從頭開始進行查找,消息分發(fā)、轉(zhuǎn)發(fā)。而Swift一定程度上減少了這種重復的勞動。當Swift的運行時的一個對象在調(diào)用方法時,編譯器會使用類似C++的虛表("vtable")來查找方法實現(xiàn)。vtable是組成Swift類的一個存儲該類所有方法的函數(shù)指針的數(shù)組,運行時可以簡單地通過下標訪問這些函數(shù)指針。于是當你的自定義代碼第一次調(diào)用方法:object.myMethod()編譯器大概會執(zhí)行如下的偽代碼:
methodImplementation = object->class.vtable[indexOfMyMethod];
methodImplementation();
可以看出簡單地使用數(shù)組下標訪問函數(shù)指針來獲取方法實現(xiàn)代碼會比消息分發(fā)的若干步驟要快捷一些。如果你的Swift方法聲明是一個final關(guān)鍵字修飾過的:@final func myMethod() { //lots of implementation code here }那對其進行查找會更快,因為Swift編譯器會直接訪問該方法實現(xiàn)的地址,這就無需運行時再去查找,完全繼承使用了靜態(tài)語言的優(yōu)勢?,F(xiàn)在如果把上面的方法中的實現(xiàn)代碼刪除,變成一個空方法:@final func myMethod() { //nothing here, blank }然后再去嘗試調(diào)用這個方法,Swift編譯器可以第一時間發(fā)現(xiàn)這個方法沒有實現(xiàn)代碼,即使你寫了相應的調(diào)用代碼,編譯器也完全不會理會,直接在完成了方法的前一句編譯就結(jié)束。通常情況下如果將某個類的實例作為方法入口參數(shù)進行傳遞,編譯器只能夠判斷這個參數(shù)的類型要么是這個類,要么是這個類的子類。因此在方法調(diào)用的時候,Swift運行時會對這個類及其子類的vtable進行查找。但如果在實例進行初始化時明確了類型,編譯階段便清楚調(diào)用的方法在哪個類中,則在運行時調(diào)用時會直接跳轉(zhuǎn)到該方法的實現(xiàn)代碼中去,省去了動態(tài)查找的步驟。以上是Swift的編譯器和運行時系統(tǒng)對方法調(diào)用的一些優(yōu)化,此外對于內(nèi)存管理,Swift除了徹底拋棄MRC使用ARC外,還對對象的生命周期進行了一定的優(yōu)化。比如你使用了一個循環(huán),循環(huán)次數(shù)是一百萬次,循環(huán)內(nèi)會創(chuàng)建一個局部的類實例,同時發(fā)送一個消息給實例對象:for _ in 0...1000000 { obj.myMethod() }這在ObjC中,選擇器發(fā)送消息的次數(shù)也會達到一百萬次。這在Swift中會有極大的不同,并且可以分成幾種情況:第一種情況,如果myMethod的方法體中沒有方法實現(xiàn)代碼,是空函數(shù),則Swift根本不會進入循環(huán)執(zhí)行方法,而是直接跳過這個循環(huán)執(zhí)行下面的代碼;第二種情況,如果方法體中有方法,同時這個obj對象自創(chuàng)建以來除了調(diào)用過myMethod方法外,并沒有被其他變量或方法使用,同時obj僅僅是在外部的函數(shù)體的花括號內(nèi)被作為局部變量創(chuàng)建和釋放,而且myMethod的作用范圍也不超過obj創(chuàng)建和釋放的范圍。在ObjC的編譯運行時環(huán)境下,obj依然會被發(fā)送一百萬次重復的消息。但是,在Swift編譯環(huán)境下,編譯器擁有足夠的信息能夠推斷出這個obj對象只會在創(chuàng)建和釋放之間進行一百萬次的方法調(diào)用這個結(jié)論后,編譯器不會分配堆內(nèi)存給obj進行初始化。相反的,編譯器會將obj創(chuàng)建在棧上。這樣,作為一個局部變量,生來僅僅是為調(diào)用myMethod()一百萬次,在棧上被分配內(nèi)存顯然會比在堆中更快更合理。此外,Swift還有對寄存器存取方法參數(shù)選擇上的優(yōu)化,把本會使用寄存器來存取的ObjC的方法中默認隱式參數(shù)self和_cmd中的_cmd去掉,相當于增加了一個給自定義入口參數(shù)的使用比棧內(nèi)存更快的寄存器存取的指標。
作者:7hriller
鏈接:http://www.itdecent.cn/p/20a2fcf230ea
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。