泛型
泛型定义
何为泛型?
泛型就是通过占位符<T> 模式,把参数的类型给参数化,这样我们就可以根据实际情况来传入参数的类型,达到简化函数的效果(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须)。
func multiNum<T: FloatingPoint> (_ x: T, _ y: T) -> T {
return x * y
}
let temp = multiNum(5.0, 6)
print(temp)
结果:
30.0
Program ended with exit code: 0
在这个示例里,我们就是定义了一个做乘积的函数,我们用泛型<T> 来实现的,因此我们在调用时,我们参数可以传递 Int、Float、Double 都是可以的,只要遵循 FloatingPoint 协议的就行。
关联类型 -- 协议泛型
在结构体中我们使用泛型是通畅无阻的,但是若是在 协议中直接使用泛型,编译器会报错。
编译器提示我们使用 关联类型进行代替,其实就是 associatedtype T
protocol SProtocol {
associatedtype T
var items: T {get}
mutating func push(_ item: T)
mutating func pop() -> T?
}
class Teacher: SProtocol {
typealias T = Int
var items: T = 0
func push(_ item: T) {
}
func pop() -> T? {
return 0
}
var name = "Tom"
}
关联类型:给协议中用到的类型一个占位符名称,协议定义中的相关类型我们都可以用这个占位符替代,等到真正实现协议的时候在去确定当前占位符的类型。
类型约束
在关联类型里,我们是使用到了associatedtype T 关键字,其实我们还可以对其添加类型约束
associatedtype T: Equatable
在这种模式中,关联类型遵循了 Element 协议,那么遵守这个协议的泛型类型的元素也必须遵守 Equatable 协议,这样就是对这个泛型的类型进行约束。
同样再例如
protocol SProtocol2: SProtocol {
associatedtype Item: SProtocol2 where Item.T == T
}
在这个协议里, Item 是一个关联类型,就像上边例子中 SProtocol 的 T 类型一样。 Item 拥有两个约束:它必须遵循 SProtocol2 协议(就是当前定义的协议),以及它的 T 类型必须是和容器里的 T 类型相同。
看看下面一个例子
func equal<T1: SProtocol2, T2: SProtocol2>(_ t1: T1,_ t2: T2)->Bool
where T1.Item == T2.Item, T2.Item: Hashable
{
return false
}
这个函数有两个形式参数, t1 和 t2 。 t1 形式参数是 T1 类型, t2 形式参 数是 T2 类型。 T1 和 T2 是两个容器类型的类型形式参数,它们的类型在调用函数时决定。 下面是函数的两个类型形式参数上设置的要求:
-
T1 必须遵循 SProtocol2 协议(写作 T1: SProtocol2 );
-
T2 也必须遵循 SProtocol2 协议(写作 C2: SProtocol2 );
-
T1 的 Item 必须和 T2 的 Item 相同(写作 T1.Item == C2.Item);
-
T2 的 Item 必须遵循 Hashable 协议(写作 T2.Item: Hashable )。 前两个要求定义在了函数的类型形式参数列表里,后两个要求定义在了函数的泛型 Where 分句 中。
这些要求意味着:
-
t1 是一个 T1 类型的容器;
-
t2 是一个 T2 类型的容器;
-
t1 和 t2 中的元素类型相同。
类型擦除
类型擦除就是 不感知 具体的类型信息,通过中间层来处理实现隐藏真正的处理逻辑。
例如:定义一个获取用户数据的实例,但是用户的类型是不一样的,有普通用户,和VIP 用户之分。
protocol DataFetch {
associatedtype DataType
func fetch(completion: ((DataType?, Error?) -> Void)?)
}
struct User {
let userId: Int
let name: String
}
struct UserData: DataFetch {
typealias DataType = User
func fetch(completion: ((DataType?, Error?) -> Void)?) {
let user = User(userId: 100, name: "Normal")
completion?(user, nil)
}
}
struct VIPData: DataFetch {
typealias DataType = User
func fetch(completion: ((DataType?, Error?) -> Void)?) {
let user = User(userId: 800, name: "VIP")
completion?(user, nil)
}
}
class MainController {
let dataFetch: ?
}
在这个例子中,dataFetch 我们要如何传递呢,咋能在 代码编写时,快速的把 UserData 或者 VIPData 传入呢,首先想到的是 把 dataFetch 申明成 DataFetch 类型。但是呢,编译器会报错
这是因为,Swift 是一门安全的语言,在协议中有关联类型时,编译器无法推断关联类型的具体类型,所以不给编译通过,所以关联类型只能用于类型约束,但是不能作为具体类型的申明。
在这个情况下,我们就想到我们能不能做个中间层?通过一个结构体struct来桥接这个关联类型。
struct AnyDataFetch<T>: DataFetch {
typealias DataType = T
private let _fetch: (((DataType?, Error?) -> Void)?) -> Void
init<U: DataFetch>(_ fetchable: U) where U.DataType == T {
_fetch = fetchable.fetch
}
func fetch(completion:((DataType?, Error?) -> Void)?) {
_fetch(completion)
}
}
在这个 AnyDataFetch 中间层中,我们实现了 DataFetch 的所有信息,通过申明 AnyDataFetch 这样的类型就能达到,一套代码,可以在普通用户和 VIP 用户共用。
class MainController {
let dataFetch: AnyDataFetch<User>
init(_ dataFetch: AnyDataFetch<User>){
self.dataFetch = dataFetch
loadData()
}
func loadData() {
self.dataFetch.fetch { (body, err) in
print((body?.name)!)
}
}
}
//普通用户
let userData = UserData()
let dataFetch = AnyDataFetch<User>.init(userData)
let VC = MainController(dataFetch)
//VIP 用户
let vipData = VIPData()
let dataFetch2 = AnyDataFetch<User>.init(vipData)
let VC2 = MainController(dataFetch2)
运行后打印结果:
通过这样的中间层,我们就可以实现协议的类型通过参数传入(依赖注入)方式,达到高效的编码方式。
这就是我们所说的 类型擦除。其实在我们的系统里,有很多是 类型擦除 的,都是 苹果给我们定义好的,例如AnySequence、AnyHashable 等。
泛型原理
首先我们先来看个例子
func multiNum<T: FloatingPoint> (_ x: T, _ y: T) -> T {
let temp = x
return temp
}
let temp = multiNum(5.0, 6)
在IR 编译后,我们看到
而
%swift.vwtable 定义如下
根据这个 vwtable 我们初步可以得到一个结构体
struct VWTable {
var func1: UnsafeRawPointer
var func2: UnsafeRawPointer
var func3: UnsafeRawPointer
var func4: UnsafeRawPointer
var func5: UnsafeRawPointer
var func6: UnsafeRawPointer
var func7: UnsafeRawPointer
var func8: UnsafeRawPointer
var size: Int
var stride: Int
var flags: UInt32
}
通过查询 swift-swift-5.5.2-RELEASE 源码,我们发现在 ValueWitness.def 中有相关的定义
所以整理后就是这个样子的
struct VWTable {
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
}
我们通过一个例子来验证下
struct VWTable {
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 size: Int
var stride: Int
var flags: UInt32
var extraInhabitantCount: UInt32
}
struct PMetadata{
var kind: Int
}
class Teacher {
var items: Int = 0
func push(_ item: Int) {
print("push")
}
var age: Int = 21
var height: Double = 175
}
let type = Teacher.self
let ptr = unsafeBitCast(type as Any.Type, to: UnsafeMutablePointer<PMetadata>.self)
let vwtPtr = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: UnsafeMutablePointer<VWTable>.self).pointee
print(vwtPtr.pointee)
然后通过 cat address 指令,打印这 8个 unknow 信息,发现和我们在源码里找到的是一样的,所以得到验证。