泛型函数
func swapTwoValues<T>(_ value1:inout T ,_ value2:inout T){
let temp = value1
value1 = value2
value2 = temp
}
var x = "str1"
var y = "Str2"
swapTwoValues(&x, &y)
泛型类型
//最简单实现,固定类型 Int
struct Stack{
private var items = [Int]()
mutating func push(_ item:Int){
items.append(item)
}
}
//泛型类型
struct Stack<T>{
private var items = [T]()
mutating func push(_ item:T){
items.append(item)
}
}
类型约束
语法:参数类型定义时,参数名称后放置单独的类或协议约束,约束与 参数名称之间使用冒号:隔开。 注意:类型的约束条件只能为类或协议。
// T约束为继承自SomeClass的类型 U约束为遵守SomeProtocol 协议的类型
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
}
在一个类型参数后面放置协议或者类,要求类型参数T遵循XX协议
当下图User没有遵循Equatable协议时,编译器报错
关联类型
在定义协议的时候,使用关联类型给协议中类型起一个占位符
关联类型的使用
protocol StackProtocal{
associatedtype Item
mutating func append(_ item:Item)
}
//指定类型
struct IntStack:StackProtocal{
var items = [Int]()
typealias Item = Int
mutating func append(_ item: Int) {
items.append(item)
}
}
//泛型
struct ElementStack<T>:StackProtocal{
var items = [T]()
typealias Item = T
mutating func append(_ item: T) {
items.append(item)
}
}
var s = IntStack()
s.append(1)
print(s.items)
非泛型协议的存储
protocol Drawable{
func draw()
}
struct Point:Drawable{
var x:Double
func draw() {
print("Point")
}
}
struct Line:Drawable{
var x:Int
func draw() {
print("Line")
}
}
// mm 的实际类型是编译器生成的一种特殊数据类型 Existential Container(存在容器)
let mm:Drawable = arc4random()%2 == 0 ? Point(x: 20.0) : Line(x:10)
mm.draw()
//Drawable 里面没有x 所以这里不能使用mm.x,也就解释了 mm.x 在编译时类型不确定的,违背了swift语法的问题
print(mm.x) //这里编译器会报错
从上述代码可以看出,mm 既可以表示 Point 类型,又可以表示 Line 类型。事实上,mm 的实际类型是编译器生成的一种特殊数据类型 Existential Container。Existential Container 对具体类型进行封装,从而实现存储一致性Existential Container 类型占据 5 个内存单元(也称 词,Word)。其结构如下图所示:
- 3个词作为Value Buffer
- 1个词作为 Value Witness Table的索引
- 1个词作为 Protocol Witness Table的索引 Value Buffer 占据3个词,存储的可能是值,也可能是指针,对于Small Value (存储空间小于等于Value Buffer),可以直接存储在Value Buffer中,对于Large Value(存储空间大于等于Value Buffer),则会在堆区分配内存空间进行存储,Value Buffer 只存储其对应的指针
VWT(Value Witness Table) 是对协议类型的生命周期管理,从而处理了具体类型的
初始化,拷贝,销毁等
PWT(Protocol Witness Table) 管理协议类型的方法调用
声明在Protocol中的方法,在底层会存储在PWT,PWT中的方法也是通过class_method,去类的V-Table中找到对应的方法的调度。
如果没有声明在Protocol中的函数,只是通过Extension提供了一个默认实现,其函数地址在编译过程中就已经确定了,对于遵守协议的类来说,这种方法是无法重写的
泛型协议的存储
- 对于class/struct/enum 其采用
类型参数的方式实现泛型 - 对于protocal,其采用
抽象类型成员Abstract Type Member的方式,具体技术称之为关联类型Associated Type接下来我们在看下泛型协议的存储
通过非泛型协议的例子,我们认为上面的代码没有问题,因为有
Existential Container类型可以保证存储一致性。因为有Existential Container类型可以保 证存储一致性。但是我们忽略类泛型协议的本质为约束类型,而不是声明类型,所以编译器会报错。而且我们还能通过下面代码再次验证
let x = gen.generate()
x的类型是什么? x类型既可以是Int类型,也可以是String类型,Swift本身是一门类型强制型语言,所有的类型必须在编译时确定,因此swift无法直接支持泛型协议的存储
类型擦除
在swift中,协议可以拥有关联类型Associated Type,用于实现类似泛型的功能, 但是关联类型的协议并不是真实类型,所以无法用作独立的类型声明。上面已经进行了讨论,在附上截图:
如何处理
对于泛型协议封装成的具体类型,事实上这是业内普遍的解决方案,swift中很多系统库都是采用这种思路解决的,具体的解决方法是:
- 定义一个中间层结构体,该结构体实现了协议的所有方法
- 在中间层结构体实现的具体协议方法中,再转发给实现协议的抽象类型
- 在中间层结构体的初始化过程中,实现协议的抽象类型,会被当做参数传入(依赖注入)