首先我們來看一段代碼
protocol Drawable {
func draw()
}
class Student: Drawable {
var x: Int = 0
var y: Int = 0
func draw() {
}
}
struct Point: Drawable {
var x: Int = 0
var y: Int = 0
func draw() {
}
}
func foo<T: Drawable>(local: T) {
bar(local: local)
}
func bar<T: Drawable>(local: T) {}
let point = Point()
foo(local: point)
上述代碼中,泛型方法的調用過程大概如下:
// 將泛型T綁定為調用方使用的具體類型铝阐,這里為Point
foo(point) --> foo<T = Point>(point)
// 在調用內部bar方法時址貌,會使用foo已經綁定的變量類型Point,可以看到徘键,
泛型T在這里已經被降級练对,通過類型Point進行取代
bar(local) --> bar<T = Point>(local)
泛型和Protocol Type的區(qū)別在于:
泛型類型由于在調用時能夠確定具體的類型,每個調用上下文只有一種類型吹害。foo和bar方法是同一種類型螟凭,在調用鏈中會通過類型降級進行類型取代。
在調用泛型方法時它呀,只需要將 Value Witness Table/ Protocol Witness Table 作為額外參數進行傳遞螺男,所以不需要使用 Extential Container。
生命周期管理 Value Witness Table
泛型類型使用 Value Witness Table 進行生命周期管理纵穿,Value Witness Table 由編譯器生成下隧,其存儲了該類型的 size、aligment(對齊方式)以及針對該類型的基本內存操作谓媒。其結構如下所示(以 C 代碼表示):
struct value_witness_table {
size_t size, align;
void (*copy_init)(opaque *dst, const opaque *src, type *T);
void (*copy_assign)(opaque *dst, const opaque *src, type *T);
void (*move_init)(opaque *dst, const opaque *src, type *T);
void (*move_assign)(opaque *dst, const opaque *src, type *T);
void (*destroy)(opaque *val, type *T);
}
注意點:
對于一個小的值類型淆院,如:integer。該類型的 copy 和 move 操作會進行內存拷貝句惯;destroy 操作則不進行任何操作土辩。
對于一個引用類型,如:class抢野。該類型的 copy 操作會對引用計數加 1拷淘;move 操作會拷貝指針,而不會更新引用計數指孤;destroy 操作會對引用計數減 1辕棚。
函數調用 Protocol Witness Table
func f<T>(_ t: T) -> T {
let copy = t
return copy
}
編譯器對上述的泛型函數進行編譯后,會得到如下代碼
void f(opaque *result, opaque *t, type *T) {
opaque *copy = alloca(T->vwt->size);
T->vwt->copy_init(copy, t, T);
T->vwt->move_init(result, copy, T);
T->vwt->destroy(t, T);
}
從生成的代碼中可以看出邓厕,方法運行時會傳入一個 type *T。很明顯扁瓢,這是一個類型參數详恼,描述泛型類型所綁定的具體類型的元信息,包括對 Value Witness Table 的索引信息引几。
步驟如下:
- 局部變量是分配在棧上的昧互,并且對于該類型挽铁,我們不知道要分配多少內存空間,所以需要通過 Value Witness Table 獲取到 T 的 size 才能進行內存分配敞掘。
- 內存空間分配完之后叽掘,通過 Value Witness Table 中的 copy 方法,以輸入值 t 來初始化局部變量玖雁。
- 局部變量初始化完畢之后更扁,通過 Value Witness Table 中的 move 方法,將局部變量移到 result 緩沖區(qū)以返回結果赫冬。
- 返回時浓镜,通過 Value Witness Table 中的 destroy 方法銷毀局部變量。
敲敲小黑板劲厌,兄die注意了!
type *T 是整個函數能夠順利運行的關鍵膛薛,那么 type *T 到底是什么呢?
編譯器會盡量在編譯時為每一個類型生成一個類型元信息對象——Type Metadata补鼻,也就是上述的 type *T哄啄。
Type Metadata
對于泛型類型來說,通過 Type Metadata 也可以索引到 Value Witness Table!
攜帶的類型元信息主要包含:類型的 Value Witness Table风范、類型的反射信息咨跌。如圖所示:
每一種類型,在全局只有一個 Type Metadata,供全局共享贝奇。
對于內建基本值類型弥臼,如:Integer,編譯器會在標準庫中生成對應的 Type Metadata 拳喻。其中Value Witness Table 是針對小的值類型 Value Witness Table。
對于引用類型猪腕,如:UIView冗澈,編譯器也會在標準庫中生成 Type Metadata。其中Value Witness Table 是針對引用類型的標準 Value Witness Table陋葡。
對于自定義的引用類型亚亲,Type Metadata 會在我們的程序中生成,Value Witness Table 則由所有引用類型共享腐缤。
編譯后的代碼是如何使用 Type Metadata 的捌归。如下所示為兩種類型對 f<T> 的調用
struct MyStruct {
var a: UInt8 = 0
var b: UInt8 = 0
}
f(123)
f(MyStruct())
當使用 int 類型和 MyStruct 類型調用 f<T> 時,編譯器生成的代碼如下所示
int val = 123;
extern type *Int_metadata;
f(&val, Int_metadata);
MyStruct val;
type *MyStruct_metadata = { ... };
f(&val, MyStruct_metadata);
通過上述代碼可以發(fā)現(xiàn) 兩者的區(qū)別在于:
int 類型使用標準庫中的 Type Metadata岭粤;
自定義類型則使用針對自身生成的 Type Metadata惜索。
上述 Type Metadata 之所以能夠在編譯時生成,是因為我們在調用時就能通過類型推導得出其類型剃浇。如果巾兆,在調用時無法推斷其類型猎物,則需要在運行時動態(tài)生成 Type Metadata!
Type Metadata 的動態(tài)生成,我們需要先來了解 Metadata Pattern
對于泛型類型角塑,編譯器會在編譯時生成一個 Metadata Pattern蔫磨。
Metadata Pattern 與 Type Metadata 的關系其實就是類與對象的關系。
以如下自定義泛型類結構為例:
struct Pair<T> {
var first: T
var second: T
}
let pa = Pair(first: 1, second: 5)
運行時根據綁定類型的 Type Metadata圃伶,結合 Metadata Pattern堤如,生成最終的確定類型的 Type Metadata。如圖所示:
- 編譯時生成一個 Pair Metadata Pattern
- 可以看出Pair<T> 為int類型, 在運行時根據綁定類型的 (Int)Type Metadata留攒,并結合 Metadata Pattern煤惩,生成最終的確定類型的 Type Metadata
我們通過一個泛型屬性訪問的例子來看看運行時是如何使用 Metadata Pattern 來生成 Type Metadata
func getSecond<T>(_ pair: Pair<T>) -> T {
return pair.second
}
編譯器生成的代碼如下:
void getSecond(opaque *result, opaque *pair, type *T) {
實例化 type metadata
type *PairOfT = get_generic_metadata(&Pair_pattern, T);
根據 Pair Type Metadata, 根據偏移字段, 獲得 second 在內存中的位置。
const opaque *second = (pair + PairOfT->fields[1]);
拷貝 second 在位置的內存到 result 緩存區(qū) ( 緩存區(qū): 函數內部 { result } )
T->vwt->copy_init(result, second, T);
返回前炼邀,銷毀局部變量魄揉。
PairOfT->vwt->destroy(pair, PairOfT);
}
在泛型類型調用方法時, Swift 會將泛型綁定為具體的類型拭宁。在編譯時就能推導出泛型類型洛退,編譯器則會進行優(yōu)化,提高運行性能! 在運行時避免通過傳遞 Type Metadata 來查找各個域的偏移杰标,從而提高運行性能!因此該實現(xiàn)的是靜態(tài)多態(tài)兵怯。在調用時能夠確定具體的類型,所以不需要使用 Extential Container腔剂。
但在協(xié)議類型調用方法時媒区,類型是 Existential Container,需要在方法內部進一步根據 Protocol Witness Table 進行方法索引掸犬,因此協(xié)議實現(xiàn)的是動態(tài)多態(tài)袜漩。
泛型特化
func min<T: Comparable>(x: T, y: T) -> T {
return y < x ? y : x
}
let a: Int = 1
let b: Int = 2
min(a, b)
上述代碼,編譯器在編譯期間就能通過類型推導確定調用 min 方法時的類型湾碎。此時宙攻,編譯器就會通過泛型特化,進行 類型取代(Type Substitute)介褥,生成如下的代碼:
func min<Int>(x: Int, y: Int) -> Int {
return y < x ? y :x
}
靜態(tài)多態(tài)在調用棧中只有一種類型!
在只有一種類型的特點座掘,來進行類型降級取代。類型降級后柔滔,產生特定類型的方法溢陪,為泛型的每個類型生成一個對應的方法。 即使這樣睛廊,我們也不用擔心會出現(xiàn)代碼空間溢出的情況嬉愧,因為是靜態(tài)多態(tài),可以進行內聯(lián)實現(xiàn)喉前,并且通過獲取上下文來進行更進一步的優(yōu)化没酣。從而降低方法數量,優(yōu)化后可以更精確卵迂,并提高性能裕便。
泛型特化是何時發(fā)生的?
在使用優(yōu)化時,調用方需要進行類型推斷见咒,這里需要知曉類型的上下文偿衰,例如類型的定義和內部方法實現(xiàn)。如果調用方和類型是單獨編譯的改览,就無法在調用方推斷類型的內部實行下翎,就無法使用優(yōu)化。
為保證這些代碼一起進行編譯宝当,這里就用到了whole module optimization视事。
而whole module optimization是對于調用方和被調用方的方法在不同文件時,對其進行泛型特化優(yōu)化的前提庆揩。
whole module optimization (全模塊優(yōu)化)
whole module optimization是用于Swift編譯器的優(yōu)化機制俐东,從 Xcode 8 開始默認開啟。
編譯器在對源文件進行語法分析之后订晌,會對其進行優(yōu)化虏辫,生成機器碼并輸出目標文件,之后鏈接器聯(lián)合所有的目標文件生成共享庫或可執(zhí)行文件锈拨。
whole module optimization通過跨函數優(yōu)化砌庄,可以進行內聯(lián)等優(yōu)化操作,對于泛型奕枢,可以通過獲取類型的具體實現(xiàn)來進行推斷優(yōu)化娄昆,進行類型降級方法內聯(lián),刪除多余方法等操作验辞。
全模塊優(yōu)化的優(yōu)勢:
- 編譯器掌握所有方法的實現(xiàn)稿黄,可以進行內聯(lián)和泛型特化等優(yōu)化,通過計算所有方法的引用跌造,移除多余的引用計數操作杆怕。
- 通過知曉所有的非公共方法,如果方法沒有被使用壳贪,就可以對其進行消除陵珍。
那么弊端則是會增加編譯時間