14年Swift推出時(shí)的主打口號就是“快”。這也十分高調(diào)地被體現(xiàn)在這門語言的名字上了道逗。其實(shí)快字主要體現(xiàn)在編譯器運(yùn)行時(shí)系統(tǒng)拇舀。新的Swift編譯器更智能变抽,能夠識別對象和方法的調(diào)用關(guān)系以及層級關(guān)系奔缠,減少對象調(diào)用方法的查找時(shí)間掠抬;同時(shí)在內(nèi)存管理上也有所提高。
首先簡要回顧一下ObjC runtime的原理校哎,ObjC使用Messaging策略——選擇器向接收器發(fā)送消息两波,編譯階段無法得知接收器對象或類是否有對應(yīng)的方法,僅將自定義的面向?qū)ο箢愋途幾g生成對應(yīng)的運(yùn)行時(shí)類型闷哆,即大量C結(jié)構(gòu)體和C函數(shù)腰奋。程序運(yùn)行時(shí)階段,運(yùn)行時(shí)系統(tǒng)根據(jù)對象的isa指針找到對象所屬的類結(jié)構(gòu)體抱怔,然后通過結(jié)合類中的緩存方法列表指針和虛函數(shù)表指針進(jìn)行查找選擇器對應(yīng)的SEL選擇器類型變量劣坊,如果找到則根據(jù)SEL變量對應(yīng)的IMP指針找到方法實(shí)現(xiàn)。若找不到對應(yīng)的方法野蝇,則會啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制讼稚,如果仍然失敗括儒,則拋出運(yùn)行時(shí)異常绕沈。
這個(gè)Messaging的過程是ObjC runtime消耗資源的一大因素。而大部分的方法調(diào)用帮寻,尤其是重復(fù)的方法調(diào)用乍狐,并沒有必要次次都從頭開始進(jìn)行查找,消息分發(fā)固逗、轉(zhuǎn)發(fā)浅蚪。而Swift一定程度上減少了這種重復(fù)的勞動(dòng)。
當(dāng)Swift的運(yùn)行時(shí)的一個(gè)對象在調(diào)用方法時(shí)烫罩,編譯器會使用類似C++的虛表("vtable")來查找方法實(shí)現(xiàn)惜傲。vtable是組成Swift類的一個(gè)存儲該類所有方法的函數(shù)指針的數(shù)組,運(yùn)行時(shí)可以簡單地通過下標(biāo)訪問這些函數(shù)指針贝攒。于是當(dāng)你的自定義代碼第一次調(diào)用方法:
object.myMethod()
編譯器大概會執(zhí)行如下的偽代碼:
methodImplementation = object->class.vtable[indexOfMyMethod];
methodImplementation();
可以看出簡單地使用數(shù)組下標(biāo)訪問函數(shù)指針來獲取方法實(shí)現(xiàn)代碼會比消息分發(fā)的若干步驟要快捷一些盗誊。如果你的Swift方法聲明是一個(gè)final關(guān)鍵字修飾過的:
@final func myMethod() { //lots of implementation code here }
那對其進(jìn)行查找會更快,因?yàn)镾wift編譯器會直接訪問該方法實(shí)現(xiàn)的地址,這就無需運(yùn)行時(shí)再去查找哈踱,完全繼承使用了靜態(tài)語言的優(yōu)勢荒适。
現(xiàn)在如果把上面的方法中的實(shí)現(xiàn)代碼刪除,變成一個(gè)空方法:
@final func myMethod() { //nothing here, blank }
然后再去嘗試調(diào)用這個(gè)方法开镣,Swift編譯器可以第一時(shí)間發(fā)現(xiàn)這個(gè)方法沒有實(shí)現(xiàn)代碼刀诬,即使你寫了相應(yīng)的調(diào)用代碼,編譯器也完全不會理會邪财,直接在完成了方法的前一句編譯就結(jié)束陕壹。
通常情況下如果將某個(gè)類的實(shí)例作為方法入口參數(shù)進(jìn)行傳遞,編譯器只能夠判斷這個(gè)參數(shù)的類型要么是這個(gè)類卧蜓,要么是這個(gè)類的子類帐要。因此在方法調(diào)用的時(shí)候,Swift運(yùn)行時(shí)會對這個(gè)類及其子類的vtable進(jìn)行查找弥奸。但如果在實(shí)例進(jìn)行初始化時(shí)明確了類型榨惠,編譯階段便清楚調(diào)用的方法在哪個(gè)類中,則在運(yùn)行時(shí)調(diào)用時(shí)會直接跳轉(zhuǎn)到該方法的實(shí)現(xiàn)代碼中去盛霎,省去了動(dòng)態(tài)查找的步驟赠橙。
以上是Swift的編譯器和運(yùn)行時(shí)系統(tǒng)對方法調(diào)用的一些優(yōu)化,此外對于內(nèi)存管理愤炸,Swift除了徹底拋棄MRC使用ARC外期揪,還對對象的生命周期進(jìn)行了一定的優(yōu)化。
比如你使用了一個(gè)循環(huán)规个,循環(huán)次數(shù)是一百萬次凤薛,循環(huán)內(nèi)會創(chuàng)建一個(gè)局部的類實(shí)例,同時(shí)發(fā)送一個(gè)消息給實(shí)例對象:
for _ in 0...1000000?{ obj.myMethod() }
這在ObjC中诞仓,選擇器發(fā)送消息的次數(shù)也會達(dá)到一百萬次缤苫。這在Swift中會有極大的不同,并且可以分成幾種情況:
第一種情況墅拭,如果myMethod的方法體中沒有方法實(shí)現(xiàn)代碼活玲,是空函數(shù),則Swift根本不會進(jìn)入循環(huán)執(zhí)行方法谍婉,而是直接跳過這個(gè)循環(huán)執(zhí)行下面的代碼舒憾;
第二種情況,如果方法體中有方法穗熬,同時(shí)這個(gè)obj對象自創(chuàng)建以來除了調(diào)用過myMethod方法外镀迂,并沒有被其他變量或方法使用,同時(shí)obj僅僅是在外部的函數(shù)體的花括號內(nèi)被作為局部變量創(chuàng)建和釋放唤蔗,而且myMethod的作用范圍也不超過obj創(chuàng)建和釋放的范圍探遵。在ObjC的編譯運(yùn)行時(shí)環(huán)境下唧瘾,obj依然會被發(fā)送一百萬次重復(fù)的消息。
但是别凤,在Swift編譯環(huán)境下饰序,編譯器擁有足夠的信息能夠推斷出這個(gè)obj對象只會在創(chuàng)建和釋放之間進(jìn)行一百萬次的方法調(diào)用這個(gè)結(jié)論后,編譯器不會分配堆內(nèi)存給obj進(jìn)行初始化规哪。相反的求豫,編譯器會將obj創(chuàng)建在棧上。這樣诉稍,作為一個(gè)局部變量蝠嘉,生來僅僅是為調(diào)用myMethod()一百萬次,在棧上被分配內(nèi)存顯然會比在堆中更快更合理杯巨。
此外蚤告,Swift還有對寄存器存取方法參數(shù)選擇上的優(yōu)化,把本會使用寄存器來存取的ObjC的方法中默認(rèn)隱式參數(shù)self和_cmd中的_cmd去掉服爷,相當(dāng)于增加了一個(gè)給自定義入口參數(shù)的使用比棧內(nèi)存更快的寄存器存取的指標(biāo)杜恰。