Swift探索( 九): 泛型

一: 泛型

1.1 什么是泛型

泛型可以將類型參數(shù)化,提高代碼復(fù)用效率代咸,減少代碼量。

1.2 泛型解決的問題

下面是一個標準的非泛型函數(shù) swapTwoInts(_:_:),用來交換兩個 Int 值:

func swapTwoInts(_ a: inout Int, _ b: inout Int)  {
    let tempA = a
    a = b
    b = tempA
}

這個函數(shù)使用輸入輸出參數(shù)(inout)來交換 ab 的值柳琢。 swapTwoInts(_:_:) 函數(shù)將 b 的原始值換成了 a,將 a 的原始值換成了 b润脸,可以調(diào)用這個函數(shù)來交換兩個 Int 類型變量

var someInt =  3
var anotherInt =  107
swapTwoInts(&someInt,  &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")

打印結(jié)果
someInt is now 107, and anotherInt is now 3

swapTwoInts(_:_:) 函數(shù)很實用柬脸,但它只能作用于 Int 類型。如果想交換兩個 String 類型值毙驯,或者 Double 類型值倒堕,你必須編寫對應(yīng)的函數(shù),類似下面 swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 函數(shù):

func swapTwoStrings(_ a: inout String, _ b: inout String)  {
    let tempA = a
    a = b
    b = tempA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double)  {
    let tempA = a
    a = b
    b = tempA
}

你可能注意到了爆价,swapTwoInts(_:_:)垦巴、swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 函數(shù)體是一樣的,唯一的區(qū)別是它們接受的參數(shù)類型(Int铭段、StringDouble)骤宣。

在實際應(yīng)用中,通常需要一個更實用更靈活的函數(shù)來交換兩個任意類型的值序愚,幸運的是憔披,泛型代碼就是為了解決這種問題而存在。

1.3 泛型基本語法

1.3.1 泛型函數(shù)

首先要指定一個占位符 T 展运,緊挨著寫在函數(shù)名后面的一對尖括號活逆,其次我們就可以使用 T 來替換任意定義的函數(shù)形式參數(shù)

func swapTwoValues<T>(_ a: inout T, _ b: inout T)  {
    let tempA = a
    a = b
    b = tempA
}

泛型版本的函數(shù)使用 占位符 類型名(這里叫做 T )精刷,而不是 實際類型名(例如 IntStringDouble )蔗候,占位符類型名并不關(guān)心 T 具體的類型怒允,但它要求 ab 必須是相同的類型,T 的實際類型由每次調(diào)用 swapTwoValues(_:_:) 來決定锈遥。

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
print("someString is now \(someString), and anotherString is now \(anotherString)")

打印結(jié)果
someInt is now 107, and anotherInt is now 3
someString is now world, and anotherString is now hello
1.3.2 泛型類型
struct IntStack {
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

這個結(jié)構(gòu)體在棧中使用一個名為 items 的數(shù)組屬性來存儲值撵摆。棧提供了兩個方法:push(_:)pop() 篡撵,用來向棧中壓入值以及從棧中移除值醋寝。這些方法被標記為 mutating 譬胎,因為它們需要修改結(jié)構(gòu)體的 items 數(shù)組。
上面的 IntStack 結(jié)構(gòu)體只能用于 Int 類型爬立。不過钾唬,可以定義一個泛型 Stack 結(jié)構(gòu)體,從而能夠處理任意類型的值侠驯。

struct Stack<Element> {
    var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

注意抡秆,Stack 基本上和 IntStack 相同,只是用占位類型參數(shù) Element 代替了實際的 Int 類型吟策。這個類型參數(shù)包裹在緊隨結(jié)構(gòu)體名的一對尖括號里( <Element> )儒士。
Element 為待提供的類型定義了一個占位名。這種待提供的類型可以在結(jié)構(gòu)體的定義中通過 Element 來引用檩坚。
在這個例子中着撩,Element 在如下三個地方被用作占位符:

  • 創(chuàng)建 items 屬性,使用 Element 類型的空數(shù)組對其進行初始化匾委。
  • 指定 push(_:) 方法的唯一參數(shù) item 的類型必須是 Element 類型拖叙。
  • 指定 pop() 方法的返回值類型必須是 Element 類型。
    由于 Stack 是泛型類型剩檀,因此可以用來創(chuàng)建適用于 Swift 中任意有效類型的棧憋沿,就像 ArrayDictionary 那樣旺芽。
    可以通過在尖括號中寫出棧中需要存儲的數(shù)據(jù)類型來創(chuàng)建并初始化一個 Stack 實例沪猴。例如,要創(chuàng)建一個 String 類型的棧采章,可以寫成 Stack<String>():
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
1.3.3 關(guān)聯(lián)類型

對于協(xié)議來說是無法像函數(shù)运嗜、類型那樣直接在協(xié)議名后添加 <T> 那樣使用泛型,需要我們使用關(guān)聯(lián)類型來代替悯舟。關(guān)聯(lián)類型通過 associatedtype 關(guān)鍵字來指定担租。

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位符名稱,其代表的實際類型在協(xié)議被遵循時才會被指定抵怎。

struct IntStack: Container {
    // IntStack 的原始實現(xiàn)部分
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // Container 協(xié)議的實現(xiàn)部分
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
1.3.4 類型約束

我們可以給泛型 T 添加特定的類型約束奋救,這將在某些情況下非常有用岭参。類型約束指定類型參數(shù)必須繼承自指定類、遵循特定的協(xié)議或協(xié)議組合尝艘。

protocol myProtocol {
    func protocolFunc()
}

func test<T: myProtocol> (_ value1: T, _ value2: T) {
    value1.protocolFunc()
    value2.protocolFunc()
}

也可以在協(xié)議里給關(guān)聯(lián)類型添加約束來要求遵循的類型滿足約束

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

要遵守 Container 協(xié)議演侯,Item 類型也必須遵守 Equatable 協(xié)議。
協(xié)議可以作為它自身的要求出現(xiàn)背亥。例如秒际,有一個協(xié)議細化了 Container 協(xié)議,添加了一個 suffix(_:) 方法狡汉。

protocol SuffixContainer: Container {
    associatedtype Suffix: SuffixContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}

在這個協(xié)議里娄徊,Suffix 是一個關(guān)聯(lián)類型,就像上邊例子中 ContainerItem 類型一樣盾戴。Suffix 擁有兩個約束:它必須遵循 SuffixContainer 協(xié)議(就是當前定義的協(xié)議)寄锐,以及它的 Item 類型必須是和容器里的 Item 類型相同。Item 的約束是一個 where 分句尖啡。
泛型 Where 分句要求了關(guān)聯(lián)類型必須遵循指定的協(xié)議锐峭,或者指定的類型形式參數(shù)和關(guān)聯(lián)類型必須相同。泛型 Where 分句以 Where 關(guān)鍵字開頭可婶,后接關(guān)聯(lián)類型的約束或類型和關(guān)聯(lián)類型一致的關(guān)系沿癞。

二:類型擦除

類型擦除 是一種非常有用的技術(shù),是將具體類型的類型信息擦除掉矛渴,只將類型的抽象信息椎扬,通常指的是類型尊從的協(xié)議、接口具温、或基類暴露出來蚕涤。我們通過下面這個案例進一步了解什么是類型擦除。

// 定義一個協(xié)議: 用于數(shù)據(jù)提取
protocol DataFetch {
    associatedtype dataType
    // 數(shù)據(jù)提取方法
    func fetchData(completion: ((Result<dataType, Error>) -> Void)?)
}

這里有一個協(xié)議 DataFetch铣猩,協(xié)議里有一個關(guān)聯(lián)類型 dataType揖铜,這個協(xié)議用來提取數(shù)據(jù)用的,它聲明里一個提取數(shù)據(jù)的方法 fetchData(completion:) 這里傳入的是一個閉包 (Result<dataType, Error>) -> Void) 接收一個 Result 類型的參數(shù)达皿,并且第一個參數(shù)就是泛型協(xié)議的關(guān)聯(lián)類型 dataType 天吓,因為我們不知道提取出來的數(shù)據(jù)是字典類型、字符串類型還是數(shù)組類型或者是其它類型峦椰。

struct UserModel {
    let uId: Int
    let userType: String
}

// 定義一個UserData  準守泛型協(xié)議 DataFetch 并且實現(xiàn)了其中的方法 fetchData 返回一個 User的數(shù)據(jù)模型
struct UserData: DataFetch {
    
    typealias dataType = UserModel
    
    func fetchData(completion: ((Result<UserModel, Error>) -> Void)?) {
        let user = UserModel(uId: 10001, userType: "普通會員")
        completion?(.success(user))
    }
}

// 定義一個VipUserData  準守泛型協(xié)議 DataFetch 并且實現(xiàn)了其中的方法 fetchData 返回一個 User的數(shù)據(jù)模型
struct VipUserData: DataFetch {
    
    typealias dataType = UserModel
    
    func fetchData(completion: ((Result<UserModel, Error>) -> Void)?) {
        let user = UserModel(uId: 10001, userType: "高級會員")
        completion?(.success(user))
    }
}

這里我定義了兩個類型 UserDataVipUserData 都準守了 DataFetch 協(xié)議并都實現(xiàn)了協(xié)議中的方法 fetchData(completion:) 但是返回的 UserModel 中的內(nèi)容不一致龄寞,一個是普通會員,一個是高級會員汤功。

當一個類中包含了一個遵守 DataFetch 協(xié)議類型的變量物邑,但這個變量的類型并不是單一的,而希望它支持遵守了 DataFetch 協(xié)議的其它類型,因為我們不需要關(guān)心數(shù)據(jù)是如何提取的色解,只關(guān)心提取數(shù)據(jù)后的結(jié)構(gòu)茂嗓。此時我們把這個變量當作該類的一個屬性或者一個方法中的參數(shù),第一時間想到的是用 DataFetch 作為類型

class homeVC {
    let userData: DataFetch
    
    init(_ userData: DataFetch){
        self.userData = userData
    }
    
    func setBaseData() {
        self.userData.fetchData  { (result) in
            switch result {
                case .success(let user):
                    print(user.userType)
                case .failure(let error):
                    print(error)
            }
        }
    }
}

此時編譯器去報錯了

DataFetch.png

這里報錯是因為:協(xié)議 DataFetch 只能用作泛型約束科阎,不能用作具體類型在抛。因為編譯器無法確定關(guān)聯(lián)類型 dataType 的具體類型是什么。如果直接將 userData 的類型改成 UserData 萧恕,從某些角度來看是這么做是可以的刚梭,但是這會在 VCUserData 對象之間創(chuàng)建一個依賴關(guān)系。如果我們遵循 SOLID 原則(簡單地說:接口職責(zé)應(yīng)該單一票唆,不要承擔(dān)過多的職責(zé)朴读。),我們希望避免依賴并隱藏實現(xiàn)細節(jié)走趋。因此這里使用 類型擦除 技術(shù)衅金。我們就需要引入一個中間層

struct AnyDataFetch<T>: DataFetch {
    
    typealias dataType = T
    
    private let _fetchData: (((Result<T, Error>) -> Void)?) -> Void
    
    init<U: DataFetch>(_ fetchable: U) where U.dataType == T {
        _fetchData = fetchable.fetchData(completion:)
    }
    
    func fetchData(completion: ((Result<T, Error>) -> Void)?) {
        _fetchData(completion)
    }
}
  • 這里我們定義了一個中間層結(jié)構(gòu)體 AnyDataFetchAnyDataFetch 實現(xiàn)了 DataFetch 的所有方法簿煌。
  • AnyDataFetch 的初始化過程中氮唯,實現(xiàn)協(xié)議的類型會被當做參數(shù)傳入(依賴注入)
  • AnyDataFetch 實現(xiàn)的具體協(xié)議方法 fetchData 中,再轉(zhuǎn)發(fā)實現(xiàn)協(xié)議的抽象類型姨伟。
    這個時候我們就可以把 AnyDataFetch 當做具體類型使用惩琉。
class homeVC {
    let userData: AnyDataFetch<UserModel>
    
    init(_ userData: AnyDataFetch<UserModel>){
        self.userData = userData
    }
    
    func setBaseData() {
        self.userData.fetchData  { (result) in
            switch result {
                case .success(let user):
                    print(user.userType)
                case .failure(let error):
                    print(error)
            }
        }
    }
}

let userData = UserData()
let anyDataFetch = AnyDataFetch<UserModel>(userData)
let vc = homeVC.init(anyDataFetch)
vc.setBaseData()

print("-----------")

let vipUserData = VipUserData()
let vipAnyDataFetch = AnyDataFetch<UserModel>(vipUserData)
let vipVC = homeVC.init(vipAnyDataFetch)
vipVC.setBaseData()


打印結(jié)果:

普通會員
-----------
高級會員

這樣做的好處就是對與 homeVC 來說不用知道當前請求的具體類型是什么( 可以是 UserData 也可以是 VipUserData ) , homeVC 接收的其實就只是 AnyDataFetch<UserModel> 類型夺荒,這其實就是所謂的 類型擦除 瞒渠。當有另一個協(xié)議的抽象類型 ( superVipUserData ) 的時候,我們不需要改變 homeVC 的代碼技扼,不需要改變 AnyDataFetch 的代碼伍玖。
系統(tǒng)中的 AnySequence , AnyCollection 都是這樣的原理。

三: 泛型的內(nèi)存結(jié)構(gòu)

3.1: 泛型內(nèi)存結(jié)構(gòu)分析

Swift探索(七): 閉包 中我們還原了函數(shù)的內(nèi)存結(jié)構(gòu)剿吻,那么在今天這篇文章中加上泛型的函數(shù)的內(nèi)存結(jié)構(gòu)又是什么樣的呢窍箍?

func test <T>(_ value: T) -> T{
    let temp = value;
    return temp
}

test(10)

通過 swiftc main.swift -emit-ir > ./main.ll 編譯成 IR 文件,并且定位到 test() 函數(shù)的調(diào)用

define hidden swiftcc void @"$s4main4testyxxlF"(%swift.opaque* noalias nocapture sret(%swift.opaque) %0, %swift.opaque* noalias nocapture %1, %swift.type* %T) #0 {
entry:
  %T1 = alloca %swift.type*, align 8
  %temp.debug = alloca i8*, align 8
  %2 = bitcast i8** %temp.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
  %value.debug = alloca %swift.opaque*, align 8
  %3 = bitcast %swift.opaque** %value.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %3, i8 0, i64 8, i1 false)
  store %swift.type* %T, %swift.type** %T1, align 8
  %4 = bitcast %swift.type* %T to i8***
  %5 = getelementptr inbounds i8**, i8*** %4, i64 -1
  %T.valueWitnesses = load i8**, i8*** %5, align 8, !invariant.load !34, !dereferenceable !35
  %6 = bitcast i8** %T.valueWitnesses to %swift.vwtable*
  %7 = getelementptr inbounds %swift.vwtable, %swift.vwtable* %6, i32 0, i32 8
  %size = load i64, i64* %7, align 8, !invariant.load !34
  %8 = alloca i8, i64 %size, align 16
  call void @llvm.lifetime.start.p0i8(i64 -1, i8* %8)
  %9 = bitcast i8* %8 to %swift.opaque*
  store i8* %8, i8** %temp.debug, align 8
  store %swift.opaque* %1, %swift.opaque** %value.debug, align 8
  %10 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 2
  %11 = load i8*, i8** %10, align 8, !invariant.load !34
  %initializeWithCopy = bitcast i8* %11 to %swift.opaque* (%swift.opaque*, %swift.opaque*, %swift.type*)*
  %12 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %9, %swift.opaque* noalias %1, %swift.type* %T) #3
  %13 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %0, %swift.opaque* noalias %9, %swift.type* %T) #3
  %14 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 1
  %15 = load i8*, i8** %14, align 8, !invariant.load !34
  %destroy = bitcast i8* %15 to void (%swift.opaque*, %swift.type*)*
  call void %destroy(%swift.opaque* noalias %9, %swift.type* %T) #3
  %16 = bitcast %swift.opaque* %9 to i8*
  call void @llvm.lifetime.end.p0i8(i64 -1, i8* %16)
  ret void
}
  • define hidden swiftcc void @"$s4main4testyxxlF"(%swift.opaque* noalias nocapture sret(%swift.opaque) %0, %swift.opaque* noalias nocapture %1, %swift.type* %T) #0 { 這里的 %swift.type* %T 就是傳入進來的泛型 T 的類型丽旅,也就是在調(diào)用時椰棘,是什么類型這里就是什么類型。
  • %T1 = alloca %swift.type*, align 8 之前的文章當中提到過 swift.type 類型魔招,其實就是 heapObject 結(jié)構(gòu)體晰搀。
  • store %swift.type* %T, %swift.type** %T1, align 8T 存儲到 T1 中五辽,這也就說明了不管是分配內(nèi)存空間還是管理這個值的內(nèi)存办斑,都是依賴于當前的類型的 Metadata
  • %5 = getelementptr inbounds i8**, i8*** %4, i64 -1 取出 -1 位置的成員
  • %T.valueWitnesses = load i8**, i8*** %5, align 8, !invariant.load !34, !dereferenceable !35 上面取出的成員就是 valueWitnesses
  • %6 = bitcast i8** %T.valueWitnesses to %swift.vwtable* 轉(zhuǎn)成成 %swift.vwtable 結(jié)構(gòu)體
    剩下的代碼就是處理 %swift.vwtable 里的東西。
    通過 IR 代碼我們不難看出泛型函數(shù)中的泛型是通過 %swift.vwtable 來進行管理內(nèi)存的乡翅。

3.2 ValueWitnessTable 值見證表

IR 代碼的最上面我們可以看到 %swift.vwtable 的結(jié)構(gòu)如下

%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }

這里的 i8* 可以把它當作 void*鳞疲,也就是這些 i8* 其實就是當前所謂的函數(shù),根據(jù)這個結(jié)構(gòu)體可以還原出 ValueWitnessTable

struct ValueWitnessesTable {
    var unknow1: UnsafeRawPointer
    var unknow2: UnsafeRawPointer
    var unknow3: UnsafeRawPointer
    var unknow4: UnsafeRawPointer
    var unknow5: UnsafeRawPointer
    var unknow6: UnsafeRawPointer
    var unknow7: UnsafeRawPointer
    var unknow8: UnsafeRawPointer
    var unknow9: Int64
    var unknow10: Int64
    var unknow11: Int32
    var unknow12: Int32
}

通過查閱各種資料后最后得出

struct ValueWitnessTable {
    var initializeBufferWithCopyOfBuffer: UnsafeRawPointer
    var destroy: UnsafeRawPointer
    var initializeWithCopy: UnsafeRawPointer
    var assignWithCopy: UnsafeRawPointer
    var initializeWithTake: UnsafeRawPointer
    var assignWithTake: UnsafeRawPointer
    var getEnumTagSinglePayload: UnsafeRawPointer
    var storeEnumTagSinglePayload: UnsafeRawPointer
    var size: Int
    var stride: Int
    var flags: UInt32
    var extraInhabitantCount: UInt32
}

綜上可知泛型函數(shù)中的泛型不管是 值類型 還是 引用類型 他的內(nèi)存結(jié)構(gòu)中都是有 ValueWitnessTable , 并且是在 metadata 的前面蠕蚜,ValueWitnessTable 保存著這個類型的 size尚洽、strideflags靶累、extraInhabitantCount 還有一些 內(nèi)存管理函數(shù) 等信息腺毫。

3.3 函數(shù)(閉包)作為泛型參數(shù)

如果是函數(shù)或者閉包做為參數(shù)傳入到泛型函數(shù)里,又會不會有什么不一樣的嗎挣柬?

func makeIncrementer() -> () -> Void {
    var runningTotal = 10
    func incrementer() {
        runningTotal += 10
    }
    return incrementer
}

func test <T>(_ value: T){
    
}

let f = makeIncrementer()

test(f)

同樣編譯成 IR 代碼定位到 main 函數(shù)的調(diào)用

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = alloca %swift.function, align 8
  %3 = bitcast i8** %1 to i8*
  %4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementeryycyF"()
  %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
  %6 = extractvalue { i8*, %swift.refcounted* } %4, 1

  store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 0), align 8
  store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 1), align 8
  %7 = bitcast %swift.function* %2 to i8*

  call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)

  %8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 0), align 8
  %9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 1), align 8

  %10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #3

  %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) #3
  %12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>*

  %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
  %.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0
  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
  store i8* bitcast (void (%swift.opaque*, %swift.refcounted*)* @"$sIeg_ytIegr_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
  %14 = bitcast %swift.function* %2 to %swift.opaque*
  %15 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$syycMD") #10
  call swiftcc void @"$s4main4testyyxlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
  %.data3 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
  %16 = load %swift.refcounted*, %swift.refcounted** %.data3, align 8
  call void @swift_release(%swift.refcounted* %16) #3
  %17 = bitcast %swift.function* %2 to i8*
  call void @llvm.lifetime.end.p0i8(i64 16, i8* %17)
  ret i32 0
}
  • %4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementeryycyF"()
    %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
    %6 = extractvalue { i8*, %swift.refcounted* } %4, 1 這三句代碼在之前的 Swift探索(七): 閉包 探究過 就是創(chuàng)建當前的閉包表達式

  • store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 0), align 8
    store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 1), align 8
    %7 = bitcast %swift.function* %2 to i8* 這三句就是存儲指針和捕獲的變量到 f里并將 f 轉(zhuǎn)換成 void* 類型

  • %8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 0), align 8
    %9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyycvp", i32 0, i32 1), align 8 去取 f 這個變量的指針和捕獲的變量

  • %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) #3
    %12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>* 開辟一塊堆區(qū)內(nèi)控空間并轉(zhuǎn)換成 { %swift.refcounted, %swift.function } 結(jié)構(gòu)體

  • %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1 取出 { %swift.refcounted, %swift.function } 結(jié)構(gòu)體的第 1 個元素 %swift.function

  • %.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0 取出 %swift.function 結(jié)構(gòu)體的第 0 個元素也就是 void* 函數(shù)地址

  • store i8* %8, i8** %.fn, align 8 將函數(shù) f 的地址存到上面的函數(shù)地址中

  • %.data = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 1
    store %swift.refcounted* %9, %swift.refcounted** %.data, align 8 取出 %swift.function 結(jié)構(gòu)體的第 1 個元素將捕獲的變量存入
    下面又進行了一系列的操作潮酒,這里其實不難發(fā)現(xiàn)在這個過程當中,對閉包又重新進行了一層包裝邪蛔。閉包的結(jié)構(gòu)體是 { i8*, %swift.refcounted* } 包裝成了 { %swift.refcounted, %swift.function } 也就是 {{ i64*, i64 } , { i8*, %swift.refcounted* }} 根據(jù)之前文章中對閉包的還原可以得到如下結(jié)構(gòu)

// 中間層
struct ReabstractionThunkContext<Context> {
    var heapObject: HeapObject
    var function: ClosureData<Context>
}

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

struct ClosureData<T>{
    var ptr: UnsafeRawPointer
    var object: UnsafePointer<T>
}

struct Box<T>{
    var object: HeapObject
    var value: T
}

由此可以得出: 當給一個泛型參數(shù)傳入一個函數(shù)時急黎,這個時候泛型 T 為函數(shù),此時它會通過重新抽象的中間層里取到函數(shù)的地址來進行執(zhí)行侧到。所以本質(zhì)上勃教,當把閉包或者函數(shù)當作泛型參數(shù)進行傳值的時候,它為了使泛型的管理統(tǒng)一匠抗,也是重新抽象了一層中間層來捕獲當前傳進來的函數(shù)故源。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汞贸,隨后出現(xiàn)的幾起案子心软,更是在濱河造成了極大的恐慌,老刑警劉巖著蛙,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件删铃,死亡現(xiàn)場離奇詭異,居然都是意外死亡踏堡,警方通過查閱死者的電腦和手機猎唁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顷蟆,“玉大人诫隅,你說我怎么就攤上這事≌寿耍” “怎么了逐纬?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長削樊。 經(jīng)常有香客問我豁生,道長兔毒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任甸箱,我火速辦了婚禮育叁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芍殖。我一直安慰自己豪嗽,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布豌骏。 她就那樣靜靜地躺著龟梦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窃躲。 梳的紋絲不亂的頭發(fā)上变秦,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音框舔,去河邊找鬼蹦玫。 笑死,一個胖子當著我的面吹牛刘绣,可吹牛的內(nèi)容都是我干的樱溉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纬凤,長吁一口氣:“原來是場噩夢啊……” “哼福贞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起停士,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挖帘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后恋技,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拇舀,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年蜻底,在試婚紗的時候發(fā)現(xiàn)自己被綠了骄崩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡薄辅,死狀恐怖要拂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情站楚,我是刑警寧澤脱惰,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站窿春,受9級特大地震影響拉一,放射性物質(zhì)發(fā)生泄漏采盒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一舅踪、第九天 我趴在偏房一處隱蔽的房頂上張望纽甘。 院中可真熱鬧良蛮,春花似錦抽碌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至皮胡,卻和暖如春痴颊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屡贺。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工蠢棱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甩栈。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓泻仙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親量没。 傳聞我的和親對象是個殘疾皇子玉转,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 一:協(xié)議 1.1 協(xié)議的定義 協(xié)議可以用來定義 方法、屬性 殴蹄、下標的聲明 究抓,協(xié)議可以被 枚舉、結(jié)構(gòu)體袭灯、類遵守(多個...
    Lee_Toto閱讀 516評論 0 1
  • 閉包是可以在你的代碼中被傳遞和引用的功能性獨立代碼塊刺下。閉包在實現(xiàn)上是一個結(jié)構(gòu)體,它存儲了一個函數(shù)(通常是其入口地址...
    HotPotCat閱讀 1,024評論 1 4
  • 協(xié)議方法的調(diào)度 protocol Incrementable{ var age: Int {set get} ...
    張?zhí)煊頮bba7閱讀 202評論 0 2
  • 一:函數(shù)類型 每個函數(shù)都有種特定的函數(shù)類型稽荧,函數(shù)的類型由函數(shù)的參數(shù)類型和返回類型組成怠李。 上述代碼中 (Double...
    Lee_Toto閱讀 582評論 0 1
  • Swift — 泛型(Generics) [TOC] 本文將介紹泛型的一些用法、關(guān)聯(lián)類型蛤克、where語句捺癞,以及對泛...
    just東東閱讀 1,316評論 0 3