泛型解读

109 阅读5分钟

泛型

泛型定义

何为泛型?

泛型就是通过占位符<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 协议的就行。

关联类型 -- 协议泛型

在结构体中我们使用泛型是通畅无阻的,但是若是在 协议中直接使用泛型,编译器会报错。

image.png

编译器提示我们使用 关联类型进行代替,其实就是 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 类型。但是呢,编译器会报错

image.png

这是因为,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)

运行后打印结果:

image.png

通过这样的中间层,我们就可以实现协议的类型通过参数传入(依赖注入)方式,达到高效的编码方式。

这就是我们所说的 类型擦除。其实在我们的系统里,有很多是 类型擦除 的,都是 苹果给我们定义好的,例如AnySequence、AnyHashable 等。

泛型原理

首先我们先来看个例子

func multiNum<T: FloatingPoint> (_ x: T, _ y: T) -> T {
    let temp = x
    return temp
}
let temp = multiNum(5.0, 6)

在IR 编译后,我们看到

image.png%swift.vwtable 定义如下

image.png

根据这个 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 中有相关的定义

image.png

image.png

image.png

image.png

image.png

所以整理后就是这个样子的

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 信息,发现和我们在源码里找到的是一样的,所以得到验证。 image.png