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