本文主要介紹泛型及其底層原理
泛型
泛型主要用于解決代碼的抽象能力 + 代碼的復(fù)用性
例如下面的例子,其中的T就是泛型
func test<T>(_ a: T, _ b: T)->Bool{
return a == b
}
//經(jīng)典例子swap,使用泛型,可以滿足不同類型參數(shù)的調(diào)用
func swap<T>(_ a: inout T, _ b: inout T){
let tmp = a
a = b
b = tmp
}
類型約束
在一個類型參數(shù)后面放置協(xié)議或者是類,例如下面的例子,要求類型參數(shù)T遵循Equatable協(xié)議
func test<T: Equatable>(_ a: T, _ b: T)->Bool{
return a == b
}
當(dāng)傳入的參數(shù)是沒有遵循Equatable協(xié)議時,會報錯
關(guān)聯(lián)類型
在定義協(xié)議時,使用關(guān)聯(lián)類型給協(xié)議中用到的類型起一個占位符名稱
- 此時的數(shù)組中的類型是Int
struct CJLStack {
private var items = [Int]()
mutating func push(_ item: Int){
items.append(item)
}
mutating func pop() -> Int?{
if items.isEmpty {
return nil
}
return items.removeLast()
}
}
- 如果想使用其他類型呢?可以
通過協(xié)議來實現(xiàn)
protocol CJLStackProtocol {
//協(xié)議中使用類型的占位符
associatedtype Item
}
struct CJLStack: CJLStackProtocol{
//在使用時,需要指定具體的類型
typealias Item = Int
private var items = [Item]()
mutating func push(_ item: Item){
items.append(item)
}
mutating func pop() -> Item?{
if items.isEmpty {
return nil
}
return items.removeLast()
}
}
where語句
where語句主要用于 表明泛型需要滿足的條件,即限制形式參數(shù)的要求,如下所示
//***********3、where語句:表明泛型需要滿足的條件
protocol CJLStackProtocol {
//協(xié)議中使用類型的占位符
associatedtype Item
var itemCount: Int {get}
mutating func pop() -> Item?
func index(of index: Int) -> Item
}
struct CJLStack: CJLStackProtocol{
//在使用時,需要指定具體的類型
typealias Item = Int
private var items = [Item]()
var itemCount: Int{
get{
return items.count
}
}
mutating func push(_ item: Item){
items.append(item)
}
mutating func pop() -> Item?{
if items.isEmpty {
return nil
}
return items.removeLast()
}
func index(of index: Int) -> Item {
return items[index]
}
}
/*
where語句
- T1.Item == T2.Item 表示T1和T2中的類型必須相等
- T1.Item: Equatable 表示T1的類型必須遵循Equatable協(xié)議,意味著T2也要遵循Equatable協(xié)議
*/
func compare<T1: CJLStackProtocol, T2: CJLStackProtocol>(_ stack1: T1, _ stack2: T2) -> Bool where T1.Item == T2.Item, T1.Item: Equatable{
guard stack1.itemCount == stack2.itemCount else {
return false
}
for i in 0..<stack1.itemCount {
if stack1.index(of: i) != stack2.index(of: i){
return false
}
}
return true
}
下面這種寫法也是可以的
//寫法二
protocol CJLStackProtocol {
//協(xié)議中使用類型的占位符
associatedtype Item
var itemCount: Int {get}
mutating func pop() -> Item?
func index(of index: Int) -> Item
}
struct CJLStack: CJLStackProtocol{
//在使用時,需要指定具體的類型
typealias Item = Int
private var items = [Item]()
var itemCount: Int{
get{
return items.count
}
}
mutating func push(_ item: Item){
items.append(item)
}
mutating func pop() -> Item?{
if items.isEmpty {
return nil
}
return items.removeLast()
}
func index(of index: Int) -> Item {
return items[index]
}
}
extension CJLStackProtocol where Item: Equatable{}
- 當(dāng)希望
泛型指定類型時擁有特定功能,可以像下面這么寫(在上述寫法二的基礎(chǔ)上增加extension)
//當(dāng)希望泛型指定類型時擁有特定功能,可以像下面這么寫
extension CJLStackProtocol where Item == Int{
func test(){
print("test")
}
}
var s = CJLStack()
s.test()
<!--打印結(jié)果-->
test
- 如果將where后的Int改成Double類型,是無法找到test函數(shù)的
泛型函數(shù)
我們在上面介紹了泛型的基本語法,下面來分析下泛型的底層原理
以下面一個簡單的泛型函數(shù)為例
//簡單的泛型函數(shù)
func testGenric<T>(_ value: T) -> T{
let tmp = value
return tmp
}
class CJLTeacher {
var age: Int = 18
var name: String = "Kody"
}
//傳入Int類型
testGenric(10)
//傳入元組
testGenric((10, 20))
//傳入實例對象
testGenric(CJLTeacher())
從上面的代碼中可以看出,泛型函數(shù)可以接受任何類型
疑問:那么泛型是如何區(qū)分不同的參數(shù),來管理不同類型的內(nèi)存呢?
查看SIL代碼,并沒有什么內(nèi)存相關(guān)的信息
-
查看IR代碼,從中可以得出
VWT中存放的是size(大?。?、alignment(對齊方式)、stride(步長)、destory、copy(函數(shù))所以VWT+PWT的存儲結(jié)構(gòu)圖示如下所示
源碼分析
在swift-source中搜索
valueWitnesses(在Metadata.h中)
對于每一個類型(Int或者自定義),都在metadata中存儲了一個VWT(用來管理當(dāng)前類型的值)繼續(xù)來到
Metadataimpl.h文件,查看其中的元組的源碼
然后回到剛開始的泛型函數(shù)testGenric
func testGenric<T>(_ value: T) -> T{
//tmp在棧上申請空間,如何知道申請多大呢?可以通過metadata中存儲的vwt得知
//copy
let tmp = value
//destory
return tmp
}
其IR代碼的詳細(xì)分析如下
; Function Attrs: argmemonly nounwind willreturn 泛型函數(shù)
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
; %swift.type* %T 表示 傳入類型的matadata
define hidden swiftcc void @"$s4main10testGenricyxxlF"(%swift.opaque* noalias nocapture sret %0, %swift.opaque* noalias nocapture %1, %swift.type* %T) #0 {
entry:
%T1 = alloca %swift.type*, align 8
%tmp.debug = alloca i8*, align 8
%2 = bitcast i8** %tmp.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
store %swift.type* %T, %swift.type** %T1, align 8
%3 = bitcast %swift.type* %T to i8***
%4 = getelementptr inbounds i8**, i8*** %3, i64 -1
; valueWitnesses 值目錄表,將其存入了 %swift.vwtable* 中
%T.valueWitnesses = load i8**, i8*** %4, align 8, !invariant.load !46, !dereferenceable !47
; 做了一個類型轉(zhuǎn)換
%5 = bitcast i8** %T.valueWitnesses to %swift.vwtable*
; 在valueWitnesses中獲取當(dāng)前這個類型的size大小
%6 = getelementptr inbounds %swift.vwtable, %swift.vwtable* %5, i32 0, i32 8
%size = load i64, i64* %6, align 8, !invariant.load !46
; 然后根據(jù)獲取的size,分配內(nèi)存空間
%7 = alloca i8, i64 %size, align 16
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %7)
%8 = bitcast i8* %7 to %swift.opaque*
; 初始化tmp的內(nèi)存空間
store i8* %7, i8** %tmp.debug, align 8
%9 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 2
%10 = load i8*, i8** %9, align 8, !invariant.load !46
; copy 拷貝
%initializeWithCopy = bitcast i8* %10 to %swift.opaque* (%swift.opaque*, %swift.opaque*, %swift.type*)*
%11 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %8, %swift.opaque* noalias %1, %swift.type* %T) #6
%12 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %0, %swift.opaque* noalias %8, %swift.type* %T) #6
%13 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 1
%14 = load i8*, i8** %13, align 8, !invariant.load !46
; destory 銷毀
%destroy = bitcast i8* %14 to void (%swift.opaque*, %swift.type*)*
call void %destroy(%swift.opaque* noalias %8, %swift.type* %T) #6
%15 = bitcast %swift.opaque* %8 to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %15)
ret void
}
所以,從IR代碼中可以得知,當(dāng)前泛型是通過ValueWitnessTable來進行內(nèi)存操作的
源碼調(diào)試
調(diào)試分為兩種,值類型和引用類型
引用類型調(diào)試
源碼調(diào)試如下
在
retain函數(shù)中加斷點調(diào)試通過lldb調(diào)試如下:
obj中存儲CJLTeacher變量
結(jié)論:對于引用類型,會調(diào)用retain進行引用計數(shù)+1,對于destory來說,就會調(diào)用release進行引用計數(shù)-1
- 泛型類型使用
VWT進行內(nèi)存管理,VWT由編譯器生成,其存儲了該類型的size、alignment以及針對該類型的基本內(nèi)存操作 - 當(dāng)對泛型類型進行內(nèi)存操作時(例如:內(nèi)存拷貝)時,最終會調(diào)用對應(yīng)泛型的VWT中的基本內(nèi)存操作
- 泛型類型不同,其對應(yīng)的VWT也不同
值類型調(diào)試
- 在
initializeWithTake方法中加斷點
結(jié)論:值類型是通過當(dāng)前內(nèi)存的copy、move來進行內(nèi)存拷貝。對于destory,內(nèi)部調(diào)用析構(gòu)函數(shù)
總結(jié)
-
對于一個
值類型,例如Integer,1、該類型的
copy和move操作會進行內(nèi)存拷貝,2、
destory操作則不進行任何操作
-
對于一個
引用類型,如class,1、該類型的
copy操作會對引用計數(shù)+1,2、
move操作會拷貝指針,而不會更新引用計數(shù);3、
destory操作會對引用計數(shù)-1
泛型函數(shù)傳入函數(shù)的分析
上面都是對變量進行的分析,那么一問來了
如果泛型函數(shù)中傳的是一個函數(shù)呢?
代碼如下所示,此時傳入的m,是傳入的整個結(jié)構(gòu)體嗎?
//如果此時傳入的是一個函數(shù)呢?
func makeIncrement() -> (Int) -> Int{
var runningTotal = 10
return {
runningTotal += $0
return runningTotal
}
}
func testGenric<T>(_ value: T){}
//m中存儲的是一個結(jié)構(gòu)體:{i8*, swift type *}
let m = makeIncrement()
testGenric(m)
- 分析IR代碼
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = alloca %swift.function, align 8
%3 = bitcast i8** %1 to i8*
; s4main13makeIncrementS2icyF 調(diào)用makeIncrement函數(shù),返回一個結(jié)構(gòu)體 {函數(shù)調(diào)用地址, 捕獲值的內(nèi)存地址}
%4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main13makeIncrementS2icyF"()
; 閉包表達式的地址
%5 = extractvalue { i8*, %swift.refcounted* } %4, 0
; 捕獲值的引用類型
%6 = extractvalue { i8*, %swift.refcounted* } %4, 1
; 往m變量地址中存值
; 將 %5 存入 swift.function*結(jié)構(gòu)體中(%swift.function = type { i8*, %swift.refcounted* })
; s4main1myS2icvp ==> main.m : (Swift.Int) -> Swift.Int,即全局變量 m
store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 0), align 8
; 將值放入 f 這個變量中,并強轉(zhuǎn)為指針
store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 1), align 8
; 將%2 強轉(zhuǎn)為 i8*(即 void*)
%7 = bitcast %swift.function* %2 to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
; 取出 function中 閉包表達式的地址
%8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 0), align 8
%9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 1), align 8
; 將返回的閉包表達式 當(dāng)做一個參數(shù)傳入 方法,所以 retainCount+1
%10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #2
; 創(chuàng)建了一個對象,存儲了 <{ %swift.refcounted, %swift.function }>*
%11 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 32, i64 7) #2
; 將 %swift.refcounted* %11 強轉(zhuǎn)成了一個結(jié)構(gòu)體類型
%12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>*
; 取出 %swift.function (最終的結(jié)果就是往 <{ %swift.refcounted, %swift.function }> 的%swift.function 中存值 ==> 做了間接的轉(zhuǎn)換與傳遞)
%13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
; 取出 <i8*, %swift.function>的首地址
%.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0
; 將 i8* 放入 i8** %.fn 中(即創(chuàng)建的數(shù)據(jù)結(jié)構(gòu) <{ %swift.refcounted, %swift.function }> 的 %swift.function 中)
store i8* %8, i8** %.fn, align 8
%.data = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 1
store %swift.refcounted* %9, %swift.refcounted** %.data, align 8
%.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0
; 將 %swift.refcounted 存入 %swift.function 中
store i8* bitcast (void (%TSi*, %TSi*, %swift.refcounted*)* @"$sS2iIegyd_S2iIegnr_TRTA" to i8*), i8** %.fn1, align 8
%.data2 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
store %swift.refcounted* %11, %swift.refcounted** %.data2, align 8
; 將%2強轉(zhuǎn)成了 %swift.opaque* 類型,其中 %2 就是 %swift.function內(nèi)存空間,即存儲的東西(函數(shù)地址 + 捕獲值地址)
%14 = bitcast %swift.function* %2 to %swift.opaque*
; sS2icMD ==> demangling cache variable for type metadata for (Swift.Int) -> Swift.Int 即函數(shù)的metadata
%15 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$sS2icMD") #9
; 調(diào)用 testGenric 函數(shù)
call swiftcc void @"$s4main10testGenricyyxlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
......
仿寫泛型函數(shù)傳入函數(shù)時的底層結(jié)構(gòu)
仿寫上述邏輯的結(jié)構(gòu)
//如果此時傳入的是一個函數(shù)呢?
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
struct FunctionData<T> {
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer<T>
}
struct Box<T> {
var refCounted: HeapObject
var value: T
}
struct GenData<T> {
var ref: HeapObject
var function: FunctionData<T>
}
func makeIncrement() -> (Int) -> Int{
var runningTotal = 10
return {
runningTotal += $0
return runningTotal
}
}
func testGenric<T>(_ value: T){
//查看T的存儲
let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
ptr.initialize(to: value)
/*
- 將 %13的值給了 %2即 %swift.function*
%13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
- 調(diào)用方法 %14 -> %2
%14 = bitcast %swift.function* %2 to %swift.opaque*
call swiftcc void @"$s4main10testGenricyyxlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
*/
let ctx = ptr.withMemoryRebound(to: FunctionData<GenData<Box<Int>>>.self, capacity: 1) {
$0.pointee.captureValue.pointee.function.captureValue
}
print(ctx.pointee.value)//捕獲的值是10
}
//m中存儲的是一個結(jié)構(gòu)體:{i8*, swift type *}
let m = makeIncrement()
testGenric(m)
<!--打印結(jié)果-->
10
所以當(dāng)是一個泛型函數(shù)傳遞過程中,會做一層包裝,意味著并不會直接的將m中的函數(shù)值、type給testGenric函數(shù),而是做了一層抽象,目的是解決不同類型在傳遞過程中的問題
總結(jié)
泛型主要用于解決
代碼的抽象能力,以及提升代碼的復(fù)用性如果一個
泛型遵循了某個協(xié)議,則在使用時,要求具體的類型也是必須遵循某個協(xié)議的在定義
協(xié)議時,可以使用關(guān)聯(lián)類型給協(xié)議中用到的類型起一個占位符名稱where語句主要用于表明泛型需要滿足的條件,即限制形式參數(shù)的要求-
泛型類型使用
VWT進行內(nèi)存管理(即通過VWT區(qū)分不同類型),VWT由編譯器生成,其存儲了該類型的size、alignment以及針對該類型的基本內(nèi)存操作- 1、當(dāng)對泛型類型進行內(nèi)存操作時(例如:內(nèi)存拷貝)時,最終會調(diào)用對應(yīng)泛型的VWT中的基本內(nèi)存操作
- 2、泛型類型不同,其對應(yīng)的VWT也不同
當(dāng)
希望泛型指定類型時擁有特定功能,可以通過extension實現(xiàn)-
對于泛型函數(shù)來說,有以下幾種情況:
-
傳入的是一個
值類型,例如Integer,1、該類型的
copy和move操作會進行內(nèi)存拷貝,2、
destory操作則不進行任何操作
-
傳入的是一個
引用類型,如class,1、該類型的
copy操作會對引用計數(shù)+1,2、
move操作會拷貝指針,而不會更新引用計數(shù);3、
destory操作會對引用計數(shù)-1
如果
泛型函數(shù)傳入的是一個函數(shù),在傳遞過程中,會做一層包裝,簡單來說,就是不會直接將函數(shù)的函數(shù)值+type給泛型函數(shù),而是做了一層抽象,主要是用于解決不同類型的傳遞問題
-