Swift类型擦除

2,435 阅读4分钟

泛型函数

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协议时,编译器报错 截屏2022-01-21 22.50.50.png

关联类型

在定义协议的时候,使用关联类型给协议中类型起一个占位符

关联类型的使用

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 ContainerExistential Container 对具体类型进行封装,从而实现存储一致性Existential Container 类型占据 5 个内存单元(也称 ,Word)。其结构如下图所示: 截屏2022-01-22 23.35.24.png

  • 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) 是对协议类型的生命周期管理,从而处理了具体类型的 初始化,拷贝,销毁等 截屏2022-01-22 23.43.27.png

PWT(Protocol Witness Table) 管理协议类型的方法调用

声明在Protocol中的方法,在底层会存储在PWT,PWT中的方法也是通过class_method,去类的V-Table中找到对应的方法的调度。 如果没有声明在Protocol中的函数,只是通过Extension提供了一个默认实现,其函数地址在编译过程中就已经确定了,对于遵守协议的类来说,这种方法是无法重写的

泛型协议的存储

  • 对于class/struct/enum 其采用类型参数的方式实现泛型
  • 对于protocal,其采用抽象类型成员 Abstract Type Member的方式,具体技术称之为关联类型 Associated Type 截屏2022-01-22 23.15.26.png 接下来我们在看下泛型协议的存储 截屏2022-01-23 20.58.18.png 通过非泛型协议的例子,我们认为上面的代码没有问题,因为有 Existential Container 类型可以保证存储一致性。因为有 Existential Container 类型可以保 证存储一致性。但是我们忽略类泛型协议的本质为约束类型,而不是声明类型,所以编译器会报错。而且我们还能通过下面代码再次验证
let x = gen.generate()

x的类型是什么? x类型既可以是Int类型,也可以是String类型,Swift本身是一门类型强制型语言,所有的类型必须在编译时确定,因此swift无法直接支持泛型协议的存储

类型擦除

在swift中,协议可以拥有关联类型Associated Type,用于实现类似泛型的功能, 但是关联类型的协议并不是真实类型,所以无法用作独立的类型声明。上面已经进行了讨论,在附上截图: 截屏2022-01-23 20.58.18.png

如何处理

对于泛型协议封装成的具体类型,事实上这是业内普遍的解决方案,swift中很多系统库都是采用这种思路解决的,具体的解决方法是:

  • 定义一个中间层结构体,该结构体实现了协议的所有方法
  • 在中间层结构体实现的具体协议方法中,再转发给实现协议的抽象类型
  • 在中间层结构体的初始化过程中,实现协议的抽象类型,会被当做参数传入(依赖注入) 截屏2022-01-23 21.48.54.png