一: 泛型
1.1 什么是泛型
泛型可以将类型参数化,提高代码复用效率,减少代码量。
1.2 泛型解决的问题
下面是一个标准的非泛型函数 swapTwoInts(_:_:)
,用来交换两个 Int
值:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let tempA = a
a = b
b = tempA
}
这个函数使用输入输出参数(inout
)来交换 a
和 b
的值。 swapTwoInts(_:_:)
函数将 b
的原始值换成了 a
,将 a
的原始值换成了 b
,可以调用这个函数来交换两个 Int
类型变量
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
打印结果
someInt is now 107, and anotherInt is now 3
swapTwoInts(_:_:)
函数很实用,但它只能作用于 Int
类型。如果想交换两个 String
类型值,或者 Double
类型值,你必须编写对应的函数,类似下面 swapTwoStrings(_:_:)
和 swapTwoDoubles(_:_:)
函数:
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(_:_:)
函数体是一样的,唯一的区别是它们接受的参数类型(Int
、String
和 Double
)。
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码就是为了解决这种问题而存在。
1.3 泛型基本语法
1.3.1 泛型函数
首先要指定一个占位符 T
,紧挨着写在函数名后面的一对尖括号,其次我们就可以使用 T
来替换任意定义的函数形式参数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let tempA = a
a = b
b = tempA
}
泛型版本的函数使用 占位符 类型名(这里叫做 T
),而不是 实际类型名(例如 Int
、String
或 Double
),占位符类型名并不关心 T
具体的类型,但它要求 a
和 b
必须是相同的类型,T
的实际类型由每次调用 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)")
打印结果
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()
}
}
这个结构体在栈中使用一个名为 items
的数组属性来存储值。栈提供了两个方法:push(_:)
和 pop()
,用来向栈中压入值以及从栈中移除值。这些方法被标记为 mutating
,因为它们需要修改结构体的 items
数组。
上面的 IntStack
结构体只能用于 Int
类型。不过,可以定义一个泛型 Stack
结构体,从而能够处理任意类型的值。
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
注意,Stack
基本上和 IntStack
相同,只是用占位类型参数 Element
代替了实际的 Int
类型。这个类型参数包裹在紧随结构体名的一对尖括号里( <Element>
)。
Element
为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 Element
来引用。
在这个例子中,Element
在如下三个地方被用作占位符:
- 创建
items
属性,使用Element
类型的空数组对其进行初始化。 - 指定
push(_:)
方法的唯一参数item
的类型必须是Element
类型。 - 指定
pop()
方法的返回值类型必须是Element
类型。
由于Stack
是泛型类型,因此可以用来创建适用于Swift
中任意有效类型的栈,就像Array
和Dictionary
那样。
可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个Stack
实例。例如,要创建一个String
类型的栈,可以写成Stack<String>():
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
1.3.3 关联类型
对于协议来说是无法像函数、类型那样直接在协议名后添加 <T>
那样使用泛型,需要我们使用关联类型来代替。关联类型通过 associatedtype
关键字来指定。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。
struct IntStack: Container {
// IntStack 的原始实现部分
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 协议的实现部分
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
添加特定的类型约束,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。
protocol myProtocol {
func protocolFunc()
}
func test<T: myProtocol> (_ value1: T, _ value2: T) {
value1.protocolFunc()
value2.protocolFunc()
}
也可以在协议里给关联类型添加约束来要求遵循的类型满足约束
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
要遵守 Container
协议,Item 类型也必须遵守 Equatable
协议。
协议可以作为它自身的要求出现。例如,有一个协议细化了 Container
协议,添加了一个 suffix(_:)
方法。
protocol SuffixContainer: Container {
associatedtype Suffix: SuffixContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
在这个协议里,Suffix
是一个关联类型,就像上边例子中 Container
的 Item
类型一样。Suffix
拥有两个约束:它必须遵循 SuffixContainer
协议(就是当前定义的协议),以及它的 Item
类型必须是和容器里的 Item
类型相同。Item
的约束是一个 where
分句。
泛型 Where
分句要求了关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。泛型 Where
分句以 Where
关键字开头,后接关联类型的约束或类型和关联类型一致的关系。
二:类型擦除
类型擦除 是一种非常有用的技术,是将具体类型的类型信息擦除掉,只将类型的抽象信息,通常指的是类型尊从的协议、接口、或基类暴露出来。我们通过下面这个案例进一步了解什么是类型擦除。
// 定义一个协议: 用于数据提取
protocol DataFetch {
associatedtype dataType
// 数据提取方法
func fetchData(completion: ((Result<dataType, Error>) -> Void)?)
}
这里有一个协议 DataFetch
,协议里有一个关联类型 dataType
,这个协议用来提取数据用的,它声明里一个提取数据的方法 fetchData(completion:)
这里传入的是一个闭包 (Result<dataType, Error>) -> Void)
接收一个 Result
类型的参数,并且第一个参数就是泛型协议的关联类型 dataType
,因为我们不知道提取出来的数据是字典类型、字符串类型还是数组类型或者是其它类型。
struct UserModel {
let uId: Int
let userType: String
}
// 定义一个UserData 准守泛型协议 DataFetch 并且实现了其中的方法 fetchData 返回一个 User的数据模型
struct UserData: DataFetch {
typealias dataType = UserModel
func fetchData(completion: ((Result<UserModel, Error>) -> Void)?) {
let user = UserModel(uId: 10001, userType: "普通会员")
completion?(.success(user))
}
}
// 定义一个VipUserData 准守泛型协议 DataFetch 并且实现了其中的方法 fetchData 返回一个 User的数据模型
struct VipUserData: DataFetch {
typealias dataType = UserModel
func fetchData(completion: ((Result<UserModel, Error>) -> Void)?) {
let user = UserModel(uId: 10001, userType: "高级会员")
completion?(.success(user))
}
}
这里我定义了两个类型 UserData
和 VipUserData
都准守了 DataFetch
协议并都实现了协议中的方法 fetchData(completion:)
但是返回的 UserModel
中的内容不一致,一个是普通会员,一个是高级会员。
当一个类中包含了一个遵守 DataFetch
协议类型的变量,但这个变量的类型并不是单一的,而希望它支持遵守了 DataFetch 协议的其它类型,因为我们不需要关心数据是如何提取的,只关心提取数据后的结构。此时我们把这个变量当作该类的一个属性或者一个方法中的参数,第一时间想到的是用 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
只能用作泛型约束,不能用作具体类型。因为编译器无法确定关联类型 dataType
的具体类型是什么。如果直接将 userData
的类型改成 UserData
,从某些角度来看是这么做是可以的,但是这会在 VC
和 UserData
对象之间创建一个依赖关系。如果我们遵循 SOLID
原则(简单地说:接口职责应该单一,不要承担过多的职责。),我们希望避免依赖并隐藏实现细节。因此这里使用 类型擦除 技术。我们就需要引入一个中间层
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)
}
}
- 这里我们定义了一个中间层结构体
AnyDataFetch
,AnyDataFetch
实现了DataFetch
的所有方法。 - 在
AnyDataFetch
的初始化过程中,实现协议的类型会被当做参数传入(依赖注入) - 在
AnyDataFetch
实现的具体协议方法fetchData
中,再转发实现协议的抽象类型。
这个时候我们就可以把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()
打印结果:
普通会员
-----------
高级会员
这样做的好处就是对与 homeVC
来说不用知道当前请求的具体类型是什么( 可以是 UserData
也可以是 VipUserData
) , homeVC
接收的其实就只是 AnyDataFetch<UserModel>
类型,这其实就是所谓的 类型擦除 。当有另一个协议的抽象类型 ( superVipUserData
) 的时候,我们不需要改变 homeVC
的代码,不需要改变 AnyDataFetch
的代码。
系统中的 AnySequence
, AnyCollection
都是这样的原理。
三: 泛型的内存结构
3.1: 泛型内存结构分析
在 Swift探索(七): 闭包 中我们还原了函数的内存结构,那么在今天这篇文章中加上泛型的函数的内存结构又是什么样的呢?
func test <T>(_ value: T) -> T{
let temp = value;
return temp
}
test(10)
通过 swiftc main.swift -emit-ir > ./main.ll
编译成 IR
文件,并且定位到 test()
函数的调用
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
的类型,也就是在调用时,是什么类型这里就是什么类型。 -
%T1 = alloca %swift.type*, align 8
之前的文章当中提到过swift.type
类型,其实就是heapObject
结构体。 -
store %swift.type* %T, %swift.type** %T1, align 8
将T
存储到T1
中,这也就说明了不管是分配内存空间还是管理这个值的内存,都是依赖于当前的类型的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*
转成成%swift.vwtable
结构体
剩下的代码就是处理%swift.vwtable
里的东西。
通过IR
代码我们不难看出泛型函数中的泛型是通过%swift.vwtable
来进行管理内存的。
3.2 ValueWitnessTable 值见证表
在 IR
代码的最上面我们可以看到 %swift.vwtable
的结构如下
%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }
这里的 i8*
可以把它当作 void*
,也就是这些 i8*
其实就是当前所谓的函数,根据这个结构体可以还原出 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
}
综上可知泛型函数中的泛型不管是 值类型 还是 引用类型 他的内存结构中都是有 ValueWitnessTable
, 并且是在 metadata
的前面,ValueWitnessTable
保存着这个类型的 size
、stride
、flags
、extraInhabitantCount
还有一些 内存管理函数 等信息。
3.3 函数(闭包)作为泛型参数
如果是函数或者闭包做为参数传入到泛型函数里,又会不会有什么不一样的吗?
func makeIncrementer() -> () -> Void {
var runningTotal = 10
func incrementer() {
runningTotal += 10
}
return incrementer
}
func test <T>(_ value: T){
}
let f = makeIncrementer()
test(f)
同样编译成 IR
代码定位到 main
函数的调用
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探索(七): 闭包 探究过 就是创建当前的闭包表达式 -
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
转换成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 }>*
开辟一块堆区内控空间并转换成{ %swift.refcounted, %swift.function }
结构体 -
%13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
取出{ %swift.refcounted, %swift.function }
结构体的第1
个元素%swift.function
-
%.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0
取出%swift.function
结构体的第0
个元素也就是void*
函数地址 -
store i8* %8, i8** %.fn, align 8
将函数f
的地址存到上面的函数地址中 -
%.data = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 1
store %swift.refcounted* %9, %swift.refcounted** %.data, align 8
取出%swift.function
结构体的第1
个元素将捕获的变量存入
下面又进行了一系列的操作,这里其实不难发现在这个过程当中,对闭包又重新进行了一层包装。闭包的结构体是{ i8*, %swift.refcounted* }
包装成了{ %swift.refcounted, %swift.function }
也就是{{ i64*, i64 } , { i8*, %swift.refcounted* }}
根据之前文章中对闭包的还原可以得到如下结构
// 中间层
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
}
由此可以得出: 当给一个泛型参数传入一个函数时,这个时候泛型 T
为函数,此时它会通过重新抽象的中间层里取到函数的地址来进行执行。所以本质上,当把闭包或者函数当作泛型参数进行传值的时候,它为了使泛型的管理统一,也是重新抽象了一层中间层来捕获当前传进来的函数。
网友评论