跳转至

泛型和关联类型

template vs generic

模板是C++泛型编程的基础。

泛型更来指一种编程思想。

为什么会有泛型

// multiNumInt(_:_:) 、 multiNumDouble(_:_:) 函数体是一样的。唯一的区别是它们接收值类型不同( Int 、 Double )。 这个时候我们想找到一个可以计算任意类型值的函数怎么办?泛型正是能让我们写出这样函数的语法。

func multiNumInt(_ x: Int, _ y: Int) -> Int{
    return x * y
}
func multiNumDouble(_ x: Double, _ y: Double) -> Double{
    return x * y
}

泛型函数

//泛型的基本写法 ,首先指定一个占位符 T ,紧挨着写在函数名后面的 一对尖括号(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须);其次我们就可以 使用 T 来替换任意定义的函数形式参数
func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T{
    return x * y
}

类型形式参数

占位符类型 T 就是一个类型形式参数的例子。类型形式参数指定并且命名一个占位符类型,紧挨着写在函数名后面的一对尖括号里(比如 <T> )。

一旦你指定了一个类型形式参数,你就可以用它定义一个函数形式参数的类型,或者用它做函数返回值类型,或者做函数体中类型标注。在不同情况下,用调用函数时的实际类型来替换类型形式参数。

可以通过在尖括号里写多个用逗号隔开的类型形式参数名,来提供更多类型形式参数。

命名类型形式参数

大多数情况下,类型形式参数的名字要有描述性,比如 Dictionary 中的 Key 和 Value ,借此告知读者类型形式参数和泛型类型、泛型用到的函数之间的关系。但是,他们之间的关系没有意义时,一般按惯例用单个字母命名,比如 T 、 U 、 V 。

类型形式参数永远用大写开头的驼峰命名法(比如 T 和 MyTypeParameter )命名,以指明它们是一个类型的占位符,不是一个值。

泛型也可以是一个闭包

闭包的本质是结构体,所以泛型也可以是一个闭包

        func makeIntcrement() -> (Int) -> Int{
            var  runningTotal = 10
            return {
                runningTotal += $0
                return runningTotal
            }
        }
        func gencr<T>(t: T){

        }
        //调用
        gencr(t: makeIntcrement())

原理探索

//还原
struct LGTeacherG{
    var age = 10
}
struct TargetMetadata{//所有的类型都是这个结构体
    var kind: Int
}
//泛型 编译器 VWT(值目击表)里面记录了size stride alignment包括内存管理的函数
//值类型来说,copy move 内存拷贝
//引用类型来说,copy 引用计数加1
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
}

var structType = LGTeacherG.self//获取类型
let ptr = unsafeBitCast(structType as Any.Type, to: UnsafeMutablePointer<TargetMetadata>.self)
let valueWitnessTable = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: UnsafeMutablePointer<ValueWitnessTable>.self).pointee//advanced移动指针位置
//print(valueWitnessTable.pointee.size)//获取LGTeacherG结构体大小

泛型类型

除了泛型函数,Swift允许你定义自己的泛型类型。它们是可以用于任意类型的自定义类、结构 体、枚举,和 Array 、 Dictionary 方式类似。

关联类型associatedtype

作用:给协议中用到的类型定义一个占位名称

//非泛型版的栈数据结构
struct LGStack{
    private var items = [Int]()
    mutating func push(_ item: Int){
        items.append(item)
    }
    mutating func pop() -> Int?{
        if items.isEmpty { return nil }
        return items.removeLast()
    }
}

//泛型版的栈数据结构
struct LGStack_New<Element>{
    private var items = [Element]()
    mutating func push(_ item: Element){
        items.append(item)
    }
    mutating func pop() -> Element?{
        if items.isEmpty { return nil }
        return items.removeLast()
    }
}

扩展泛型类型

当你扩展一个泛型类型时,不需要在扩展的定义中提供类型形式参数列表。原始类型定义的类 型形式参数列表在扩展体里仍然有效,并且原始类型形式参数列表名称也用于扩展类型形式参 数。

extension LGStack_New {
  var topItem: Element? {
    return items.isEmpty ? nil : items[items.count - 1]
  }
}

类型约束

有时在用于泛型函数的类型和泛型类型上,强制其遵循特定的类型约束很有用。类型约束指出一个类型形式参数必须继承自特定类,或者遵循一个特定的协议、组合协议。

例如,Swift的 Dictionary 类型在可以用于字典中键的类型上设置了一个限制。字典键的类型必须是是可哈希的。也就是说,它必须提供一种使其可以唯一表示的方法。 Dictionary 需要它的键是可哈希的,以便它可以检查字典中是否包含一个特定键的值。没有了这个要 求, Dictionary 不能区分该插入还是替换一个指定键的值,也不能在字典中查找已经给定的键的值。

类型约束语法

在一个类型形式参数名称后面放置一个类或者协议作为形式参数列表的一部分,并用冒号隔开,以写出一个类型约束。下面展示了一个泛型函数类型约束的基本语法(和泛型类型的语法相同)

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    //func body
}

类型约束的应用

这是一个叫做 findIndex(ofString:in:) 的非泛型函数,在给定的 String 值数组中查找给定的 String 值。findIndex(ofString:in:) 函数返回一个可选的 Int 值,如果找到了给定字符串,返回数组中第一个匹配的字符串的索引值,如果找不到给定字符串就返回 nil

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

//泛型版本
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

Equatable 的协议

Swift 标准库中定义了一个叫做 Equatable 的协议,要求遵循其协议的类型要实现相等操作符( == )和不 等操作符( != ),用于比较该类型的任意两个值。所有Swift标准库中的类型自动支持 Equatable 协议。

任何 Equatable 的类型都能安全地用于 findIndex(of:in:) 函数,因为可以保证那些类型支持相等操作符。 为了表达这个事实,当你定义函数时将 Equatable 类型约束作为类型形式参数定义的一部分书写。

抽取相同的行为定一个协议

protocol StackProtocol_old {
    var itemCount: Int{ get }
    mutating func pop() -> Int?
    func index(of index: Int) -> Int
}

关联类型

//上面定义协议的时候需要指明当前类型,那么能不能给 Protocol 也上一个泛型?
//这样一些就会出现一个错误,系统提示 Protocol 不支持泛型参数,需要我们使用关联类型来代替。

// MARK: 关联类型associatedtype 占位类型  类型别名typealias
// 上面的协议支持的数据类型只有Int,如果想要支持任意类型,需要使用关联类型
// 关联类型给协议中用到的类型一个占位符名称,协议定义中的相关类型我们都可以用这个占位符 替代,等到真正实现协议的时候在去确定当前占位符的类型
protocol StackProtocol {
    associatedtype Item//关联类型
    var itemCount: Int{ get }
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}

//关联类型的应用
struct HHStack: StackProtocol{
    typealias Item = Int//指定Item类型
    private var items = [Item]()
    var itemCount: Int{
        get{
            return items.count
        }
    }
    mutating func push(_ item: Item){
        items.append(item)
    }
    mutating func pop() -> Item?{
        if items.isEmpty { return nil }
        return items.removeLast()
    }
    func index(of index: Int) -> Item {
        return items[index]
    }
}

类型别名

为已存在类型定义的一个可选择的名字。类似OC中的typedef。

关键字typealias定义一个类型的别名

当你想通过在一个上下文中更合适可具有表达性的名字来引用一个已存在的类型时,别名就非常有用了

//AudioSample音频采样率
typealias AudioSample = UInt8
let sample: AudioSample = 32
//定义一个闭包的别名
typealias HHBlock = (_ index: Int) -> Void

关联类型添加约束

//1、关联类型添加约束
protocol StackProtocol_yueshu {
    associatedtype Item: FixedWidthInteger//给当前的关联类型添加约束,比如我们要求 Item 必须都要遵循 FixWidthInteger
    var itemCount: Int{ get }
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}
//2、也可以直接在约束中使用协议
protocol EvenProtocol: StackProtocol{
    //在这个协议里, Even 是一个关联类型。
    //Even 拥有两个约束
    //1. 它必须遵循 EvenProtocol 协议(就是当前定义的协议)
    //2. 它的 Item 类型必须是和容器里的 Item 类型相同。 Item 的约束是一个 where 分句。
    //where 分句,泛型 Where 分句要求了关联类型必须遵循指定 的协议,或者指定的类型形式参数和关联类型必须相同。泛型 Where 分句以 Where 关键字开 头,后接关联类型的约束或类型和关联类型一致的关系。
    associatedtype Even: EvenProtocol where Even.Item == Item//where限定语句。

    //协议中添加新的方法
    func pushEven(_ item: Int) -> Even
}

extension HHStack: EvenProtocol{
    func pushEven(_ item: Int) -> HHStack {
        var result = HHStack()
        if item % 2 == 0{
            result.push(item)
        }
        return result
    }
}

// MARK: 判断stack是否相等的方法案例
/// T1和T2遵循StackProtocol协议
/// T1.Item和T2.Item类型相同
/// T1类型的Item遵循Equatable协议
func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1, _ stack2: T2)-> Bool where T1.Item == T2.Item, T1.Item: Equatable {
    guard stack1.itemCount == stack2.itemCount else {
        return false
    }
    for i in 0..<stack1.itemCount {
        if stack1.index(of: i) != stack2.index(of: i) {
            return false
        }
    }
    return true
}

类型擦除

用处:

AnySequence和AnyCollection把Sequence协议Collection协议封装一层。通过抽象中间层,隐藏具体类型。

// MARK: 泛型的协议
protocol DataFetch {
    associatedtype dataType
    func fetch(completion: ((Result<dataType, Error>) -> Void)?)
}
//请求用户数据
struct User {//用户模型
    let userId: Int
    let name: String
}

struct UserFetch: DataFetch {
    typealias dataType = User//规定类型是User类型的
    func fetch(completion: ((Result<dataType, Error>) -> Void)?) {
        let user = User(userId: 1001, name: "Kody")
        completion?(.success(user))
    }
}
struct VIPFetch: DataFetch {
    typealias dataType = User
    func fetch(completion: ((Result<dataType, Error>) -> Void)?) {
        let user = User(userId: 0001, name: "VIP")
        completion?(.success(user))
    }
}

// 通过AnyDataFetch中间层 请求用户数据和VIP数据,减少耦合
/// 这里我们定义了一个中间层结构体 AnyDataFetch , AnyDataFetch 实现了 DataFetch 的所有方法。
/// 在 AnyDataFetch 的初始化过程中,实现协议的类型会被当做参数传入(依赖注入)
/// 在 AnyDataFetch 实现的具体协议方法 fetch 中,再转发实现协议的抽象类型。
struct AnyDataFetch<T>: DataFetch {//AnyDataFetch中间层,接受一个T具体类型,遵循DataFetch协议实现方法
    typealias DataType = T
    private let _fetch: (((Result<T, Error>) -> Void)?) -> Void
    init<U: DataFetch>(_ fetchable: U) where U.dataType == T {//初始化的时候 把具体类型和协议类型传进来
        _fetch = fetchable.fetch
    }
    func fetch(completion: ((Result<T, Error>) -> Void)?) {
        _fetch(completion)
    }
}



//测试 模拟请求用户数据
let userFetch = UserFetch()//初始化userData
let anyDataFetch = AnyDataFetch<User>(userFetch)

let vipFetch = VIPFetch()
let vipDataFetch = AnyDataFetch<User>(vipFetch)

func test_fetch() {
    let userData: AnyDataFetch<User>//初始化 指明泛型是User。隐藏了userData具体的UserData类型

}