美文网首页Swift基础
Swift -- 10.泛型&集合

Swift -- 10.泛型&集合

作者: MissStitch丶 | 来源:发表于2022-02-25 15:59 被阅读0次

    一.泛型语法

    1.函数中使用泛型

    泛型的基本语法,首先我们要指定一个占位符T(占位符也可以为其它字符),紧挨着写在函数名后面的一对尖括号(当前我们这个T要遵循FloatingPoint协议,计算乘积所必须);其次我们就可以使用T来替换任意定义的函数形式参数。

    func multiNum<T: FloatingPoint>(x: T, y: T) -> T {
        return x * y
    }
    

    2.结构体/类使用泛型

    struct LGStack<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()
        }
    }
    

    结构体与类使用泛型用法一致

    3.协议使用泛型

    注意:Protocol不支持泛型参数,需要我们使用关联类型associatedtype来代替。

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

    需要在遵循协议的地方,使用typealias Item = Int来明确泛型参数的类型

    protocol StackProtocol {
        
        associatedtype Item
        
        var itemCount: Item{ get }
        
        mutating func pop() -> Item?
        
        func index(of index: Int) -> Item
    }
    
    struct LGStack: StackProtocol{
        
        //确定协议中的泛型参数类型
        typealias Item = Int
        
        var itemCount: Int {
            get {
                return items.count
            }
        }
        
        private var items = [Item]()
        
        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) -> Int {
            return items[index]
        }
    }
    

    当然也可以给关联类型添加约束,比如Item必须遵循FixedWidthInteger。加上过后,传入的Item就必须遵守FixedWidthInteger协议

    associatedtype Item: FixedWidthInteger
    

    此时添加一个EvenProtocol

    protocol EvenProtocol: StackProtocol {
        //1.Even必须遵循EventProtocol协议,
        //2.限定Even关联类型里边的Item和当前的StackProtocol中Item类型必须是一致的
        associatedtype Even: EvenProtocol where Even.Item == Item
        
        func pushEven(_ item: Int) -> Even
    }
    
    extension LGStack: EvenProtocol{
        
    //    //方式1,直接指名函数中Even的类型为EvenProtocol
    //    func pushEven(_ item: Int) -> LGStack {
    //        var result = LGStack()
    //        if item % 2 == 0{
    //            result.push(item)
    //        }
    //        return result
    //    }
        
        
        //方式二
        typealias Even = LGStack
            
        func pushEven(_ item: Int) -> Even {
            //1.这里必须传入遵循EvenProtocol协议的数据
            //2.传入的数据Item类型必须要和当前Item类型一致
            var result = LGStack()
            if item % 2 == 0{
                result.push(item)
            }
            return result
        }
    }
    
    

    在这个协议里,Even是关联类型。拥有2个约束:它必须信息EventProtocol,它的Item类型必须和容器里的Item类型相同。简单来说,就是传入的参数中的Item类型必须与当前Item类型一致

    where要求了关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。泛型where分句以where关键字开头,后接关联类型的约束或类型和关联类型一致的关系。

    //T1和T2必须遵守StackProtocol协议
    //T1的关联类型Item必须和T2的Item一致
    //T1必须遵循Equatable协议
    //当然T1: StackProtocol, T2: StackProtocol,类型声明也可以定义到where后
    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: Int(i)) != stack2.index(of: Int(i)) {
                return false
            }
        }
        return true
    }
    

    二.类型擦除

    1.类型擦除

    //定义了一个协议
    protocol DataFetch {
        associatedtype DataType
        
        func fetch(completion: ((Result<DataType, Error>) -> Void)?)
    }
    
    //用户数据
    struct User {
        let userId: Int
        let name: String
    }
    
    struct UserData: DataFetch {
        
        //关联类型为User
        typealias DataType = User
        
        func fetch(completion: ((Result<DataType, Error>) -> Void)?) {
            let user = User(userId: 1001, name: "Kody")
            completion?(.success(user))
        }
    }
    
    class someViewController{
        /*
         此时let userData: DataFetch会报错
         Swift是一门类型安全的语言,此时的userData传入的DataFetch的泛型协议类型,
         由于关联类型并不能确定,因此编译器会报错
         
         那么我们该怎么解决这个问题?
         
         我们传入UserData有问题吗?
         
         对于ViewController来说,并不关心你的数据类型是UserData还是其它Data,
         只关心最后获取的User,也就是获取的数据。
         如果我们还有VipData,如果传入UserData,那么Controller与UserData的耦合性就比较严重了。
         */
        let userData: DataFetch
        
        init() {
        }
    }
    

    此时就需要一个中间层AnyDataFetch进行类型擦除,解决ViewController与数据的耦合问题

    //定义一个协议,获取数据的协议DataFetch
    protocol DataFetch {
        associatedtype DataType
        
        func fetch(completion: ((Result<DataType, Error>) -> Void)?)
    }
    
    //定义一个User类
    struct User {
        let uid: Int
        let name: String
    }
    
    //获取UserData的类
    struct UserData: DataFetch {
        
        typealias DataType = User
        
        func fetch(completion: ((Result<User, Error>) -> Void)?) {
            let user = User(uid: 1001, name: "Kody")
            completion?(.success(user))
        }
    }
    
    /*
     定义一个中间类AnyDataFetch
     
     AnyDataFetch实现了DataFetch的所有方法
     在AnyDataFetch初始化过程中,实现协议的类型会被当做参数传入(依赖注入)
     在AnyDataFetch实现具体方法fetch中,再转发实现协议的抽象类型。
     */
    struct AnyDataFetch<T>: DataFetch {
        
        typealias DataType = T
        
        //保存了当前类型的fetch函数,如果是UserData,那么保存的是UserData的fetch
        private let _fetch: (((Result<T, Error>) -> Void)?) -> Void
        
        //实现协议的类型会被当做参数传入(依赖注入)
        init<U: DataFetch>(_ completion: U) where U.DataType == T  {
            _fetch = completion.fetch
        }
        
        //执行保存的fetch函数
        func fetch(completion: ((Result<T, Error>) -> Void)?) {
            _fetch(completion)
        }
    }
    
    class someViewController {
        /*
         此时使用的AnyDataFetch就已经把UserData类型擦除了
         无论是UserData还是VipData都与someViewController没有耦合
         
         通过中间层AnyDataFetch把具体类型给擦除了(隐藏掉了),通过这种方式就叫做类型擦除
         */
        
        //具体类型为UserData,通过抽象隐藏了具体类型
        let userData: AnyDataFetch<User>
        
        init(_ userData: AnyDataFetch<User>) {
            self.userData = userData
        }
    }
    
    let userData = UserData()
    
    let vc = someViewController(AnyDataFetch<User>(userData))
    
    vc.userData.fetch { result in
        switch result {
        case .success(let user):
            print(user.name) // Kody
        case .failure(let error):
            print(error)
        }
    }
    
    //如果再添加一个VipData
    
    struct VipData: DataFetch {
        typealias DataType = User
        
        func fetch(completion: ((Result<User, Error>) -> Void)?) {
            let vipUser = User(uid: 100, name: "Vip_Kody")
            completion?(.success(vipUser))
        }
    }
    
    let data = VipData()
    
    let vc1 = someViewController(AnyDataFetch<User>(data))
    
    vc1.userData.fetch { result in
        switch result {
        case .success(let user):
            print(user.name) // Vip_Kody
        case .failure(let error):
            print(error)
        }
    }
    
    /*
     此时你会发现,当我们增加VipData的数据类型时,我们的someViewController是不需要做任何的改动的
     
     这样的效果,我们就称为类型擦除
     
     
     关键点:使用AnyFetch作为中间层,传入了遵守协议的类型(依赖注入),存储了协议函数,使用AnyFetch执行函数,达到类型擦除的效果。
     
     依赖注入:实现协议的类型当做参数传入
     
     实际上,就是保存了函数,执行函数,把类型给擦除了的一个过程。
     */
    

    通过中间层AnyDataFetch把具体类型给擦除了(隐藏掉了),这种方式就叫做类型擦除

    其实在Swift中很多地方也使用到了类型擦除,比如:Codable源码AnySequenceAnyCollection

    AnySequenceAnyCollection隐藏了泛型协议,统一的表达SequenceCollection

    2.AnySequence使用案例

    需求:迭代你的自定义属性User,其中User属性如下:

    struct User {
        var userId: Int
        var name: String
    }
    

    使用Sequence迭代自定义属性

    //继承自Sequence才能够被便利/迭代
    struct User: Sequence {
        var userId: Int
        var name: String
        
        /*
         必须实现的协议方法,返回一个迭代器(遵循IteratorProtocol)
         */
        func makeIterator() -> CustomIterator {
            return CustomIterator(obj: self)
        }
    }
    
    //迭代器
    struct CustomIterator: IteratorProtocol {
        
        var children: Mirror.Children
        
        init(obj: User) {
            children = Mirror(reflecting: obj).children
        }
    
        /*
         必须实现的方法,返回迭代时的内容
         该函数会循环执行,当返回值为nil时,循环结束
         */
        mutating func next() -> String? {
            guard let child = children.popFirst() else { return nil }
            return "\(child.label ?? "null") is \(child.value)"
        }
    }
    
    let user = User(userId: 10, name: "小明")
    
    for obj in user {
        print(obj)
    }
    
    /*
     打印结果
     
     userId is 10
     name is 小明
     */
    

    如果这个时候,另一个自定义属性Vip也需要迭代

    struct Vip {
        var vipdate: String
        var viplevel: Int
        var vipName: String
    }
    

    那么我们是否也像User一样给Vip添加一个Sequence协议呢?

    当然,这样做是可以的,但是这样做并不是我们想要的方式。

    对于VipUser来说它们的行为都是一致的,所以这里我们希望抽象出一个统一的协议

    struct User: CustomDataSequence {
        var userId: Int
        var name: String
    }
    
    struct Vip: CustomDataSequence {
        var vipdate: String
        var viplevel: Int
        var vipName: String
    }
    
    struct CustomDataIterator: IteratorProtocol {
        
        var children: Mirror.Children
        
        init(obj: Any) {
            children = Mirror(reflecting: obj).children
        }
    
        /*
         必须实现的方法,返回迭代时的内容
         该函数会循环执行,当返回值为nil时,循环结束
         */
        mutating func next() -> String? {
            guard let child = children.popFirst() else { return nil }
            return "\(child.label ?? "null") is \(child.value)"
        }
    }
    
    protocol CustomDataSequence: Sequence {}
    
    extension CustomDataSequence {
        func makeIterator() -> CustomDataIterator {
            return CustomDataIterator(obj: self)
        }
    }
    

    如果此时我们想要定义个数组,同时有UserVip数据

    let user = User(userId: 10, name: "小明")
    
    let vip = Vip(vipdate: "2022-02-24", viplevel: 100, vipName: "Vip3")
    
    /*
     如果这样使用的话会报错,和之前讲到的let data: DataFetch情况类似
     对于协议泛型来说,并不能确定关联类型,因此编译器会报错
     */
    let datas: [CustomDataSequence] = [user, vip]
    

    这个时候就需要使用到AnySequence将具体的Sequence类型隐藏了,调用者知知道数组中的元素是一个可以迭代输出字符串类型的序列

    //这里使用AnySequence作为中间类,来擦除泛型协议类型
    let datas: [AnySequence<String>] = [AnySequence(user), AnySequence(vip)]
    
    for obj in datas {
        for item in obj {
            print(item)
        }
    }
    
    /*
     打印结果
     
     userId is 10
     name is 小明
     vipdate is 2022-02-24
     viplevel is 100
     vipName is Vip3
     */
    

    这就是AnySequence上的类型擦除案例

    三.泛型的工作原理

    //了解泛型工作原理
    
    /*
     此时的编译器并不知道T是什么类型,可能是引用类型可能是值类型
     */
    func testGeneric<T>(_ value: T) -> T {
        //对于我们的编译器来说,类型并不明确,编译器怎么知道temp应该分配多大的内存空间、步长、对齐的字段是多少
        let temp = value
        return temp
    }
    
    testGeneric(10)
    

    猜想:此时应该是根据传入的类型来决断分配的内存空间大小

    那么我们编译成IR代码来看看到底是怎么工作的

    //testGeneric函数,实际上把%swift.type*传入进来了(metadata)
    define hidden swiftcc void @"$s4main11testGenericyxxlF"(%swift.opaque* noalias nocapture sret(%swift.opaque) %0, %swift.opaque* noalias nocapture %1, %swift.type* %T) #0 {
    entry:
      //%swift.type = type { i64 } ,Metadata的结构体
      %T1 = alloca %swift.type*, align 8
    
      %temp.debug = alloca i8*, align 8
      %2 = bitcast i8** %temp.debug to i8*
      call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
    
      //将T(函数的参数metadata)传入到我们的T1
      store %swift.type* %T, %swift.type** %T1, align 8
    
      %3 = bitcast %swift.type* %T to i8***
    
      //注意:这里对%3去-1索引存到%4
      %4 = getelementptr inbounds i8**, i8*** %3, i64 -1
    
      //取出来的-1索引就是valueWitnessTable(值目击表,记录了size、stride、alignment)
      %T.valueWitnesses = load i8**, i8*** %4, align 8, !invariant.load !32, !dereferenceable !33
    
      %5 = bitcast i8** %T.valueWitnesses to %swift.vwtable*
      %6 = getelementptr inbounds %swift.vwtable, %swift.vwtable* %5, i32 0, i32 8
      %size = load i64, i64* %6, align 8, !invariant.load !32
      %7 = alloca i8, i64 %size, align 16
      call void @llvm.lifetime.start.p0i8(i64 -1, i8* %7)
      %8 = bitcast i8* %7 to %swift.opaque*
      store i8* %7, i8** %temp.debug, align 8
    
      //%9取出valueWitnesses中索引2的值
      %9 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 2
    
      //%10由%9得来,i8**变为i8*
      %10 = load i8*, i8** %9, align 8, !invariant.load !32
    
      //使用Copy初始化函数,这样可以发现,都是通过valueWitness来初始化的
      %initializeWithCopy = bitcast i8* %10 to %swift.opaque* (%swift.opaque*, %swift.opaque*, %swift.type*)*
      %11 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %8, %swift.opaque* noalias %1, %swift.type* %T) #3
      %12 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %0, %swift.opaque* noalias %8, %swift.type* %T) #3
    
      //%13取出valueWitnesses中索引1的值
      %13 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 1
      //%14由%13得来,i8**变为i8*
      %14 = load i8*, i8** %13, align 8, !invariant.load !32
    
      //销毁函数,传入%14
      %destroy = bitcast i8* %14 to void (%swift.opaque*, %swift.type*)*
      call void %destroy(%swift.opaque* noalias %8, %swift.type* %T) #3
      %15 = bitcast %swift.opaque* %8 to i8*
      call void @llvm.lifetime.end.p0i8(i64 -1, i8* %15)
      ret void
    }
    

    所以,泛型函数是如何管理内存的呢?

    其实就是使用了Value Witness Table(VWT)

    1.还原ValueWitnessTable

    我们再来看看VWT的数据结构

    //i8*就是函数地址
    %swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }
    

    那么我们通过VWT的数据结构来还原一下VWT

    //%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }
    struct ValueWitnessTable {
        var initializeBufferWithCopyOfBuffe: UnsafeRawPointer
        //看到这里就能和我们之前IR代码里的逻辑对应上了
        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: Int
    }
    
    struct LGTeacher {
        var age = 10
    }
    
    struct TargetMetadata {
        //这里就写第一个就行了,因为ValueWitnessTable在metadata内存地址的前8字节,所以后续的可以忽略
        var kind: Int
    }
    
    let ptr = unsafeBitCast(LGTeacher.self as Any.Type, to: UnsafePointer<TargetMetadata>.self)
    
    let vwtPtr = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: UnsafePointer<ValueWitnessTable>.self).pointee
    
    print(vwtPtr.pointee.size) // 8,也就是结构体的大小
    
    /*
     可以通过cat address 还原unkown1~unkown8的函数名称,上面的我已经还原了
     */
    

    记录在metadata内存地址的前8个字节,存放VWT。来解决使用泛型时出现的内存分配问题

    总结:

    • 泛型是通过VWT(编译器生成的)管理内存的。
    • VWT存储了sizestridealignment。换句话理解,当我们创建值类型数据时,编译器就是通过VWT知道该分配多大的内存空间
    • 值类型来说,通过源码中(copymove) 进行内存的拷贝
    • 引用类型来说,copy引用计数加1

    2.泛型参数传入闭包表达式

    //如果函数中的泛型参数传入的是闭包呢?
    
    //结构体{i8*, i64(捕获值)}
    func makeIncreament() -> (Int) -> Int {
        var runningTotal = 10
        return {
            runningTotal += $0
            return runningTotal
        }
    }
    
    func generic<T>(t: T) {
        
    }
    
    var f = makeIncreament()
    
    generic(t: f)
    

    转化到IR代码分析

    define i32 @main(i32 %0, i8** %1) #0 {
    entry:
      %2 = alloca %swift.function, align 8
      %access-scratch = alloca [24 x i8], align 8
      %3 = bitcast i8** %1 to i8*
    
      //返回闭包结构体
      %4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main14makeIncreamentS2icyF"()
      %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
      %6 = extractvalue { i8*, %swift.refcounted* } %4, 1
    
      //s4main1fyS2icvp -> f变量
      //存入函数地址及堆区空间到f
      store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 0), align 8
      store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 1), align 8
    
      %7 = bitcast %swift.function* %2 to i8*
      call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
      %8 = bitcast [24 x i8]* %access-scratch to i8*
      call void @llvm.lifetime.start.p0i8(i64 -1, i8* %8)
      call void @swift_beginAccess(i8* bitcast (%swift.function* @"$s4main1fyS2icvp" to i8*), [24 x i8]* %access-scratch, i64 32, i8* null) #2
    
      //%9为从f取出的闭包函数地址
      %9 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 0), align 8
    
      //%10为从f取出的捕获值堆区空间
      %10 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 1), align 8
      %11 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %10) #2
      call void @swift_endAccess([24 x i8]* %access-scratch) #2
      %12 = bitcast [24 x i8]* %access-scratch to i8*
      call void @llvm.lifetime.end.p0i8(i64 -1, i8* %12)
    
      //创建堆区空间
      %13 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 32, i64 7) #2
      
      //强转至{ %swift.refcounted, %swift.function }
      %14 = bitcast %swift.refcounted* %13 to <{ %swift.refcounted, %swift.function }>*
    
      //取出%swift.function中i8*到%.fn
      %15 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %14, i32 0, i32 1
      %.fn = getelementptr inbounds %swift.function, %swift.function* %15, i32 0, i32 0
    
      //将函数地址%9存入%.fn
      store i8* %9, i8** %.fn, align 8
    
      //取出%swift.function中堆区空间到%.fn
      %.data = getelementptr inbounds %swift.function, %swift.function* %15, i32 0, i32 1
      //存入%10到%.data
      store %swift.refcounted* %10, %swift.refcounted** %.data, align 8
    
      /*
        看到这里,已经明白了逻辑。此时泛型参数为闭包表达式时
        编译器会开辟一个堆空间(中间层){ %swift.refcounted, %swift.function }
        将闭包表达式数据存入index 1
      */
    
      //%2的index 0地址给%.fn1
      %.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0
    
      //$sS2iIegyd_S2iIegnr_TRTA ---> partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@unowned Swift.Int) 
      //-> (@unowned Swift.Int) to @escaping @callee_guaranteed (@in_guaranteed Swift.Int) -> (@out Swift.Int)
      //
      //reabstraction 再抽象,存入的再抽象闭包地址
      //也就是将闭包的地址传入%2的i8*中
      store i8* bitcast (void (%TSi*, %TSi*, %swift.refcounted*)* @"$sS2iIegyd_S2iIegnr_TRTA" to i8*), i8** %.fn1, align 81
    
      //将创建的堆区空间(中间层)存入%2中%swift.refcounted*
      %.data2 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
      store %swift.refcounted* %13, %swift.refcounted** %.data2, align 8
    
      %16 = bitcast %swift.function* %2 to %swift.opaque*
      %17 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$sS2icMD") #9
    
      //$s4main7generic1tyx_tlF ---> main.generic<A>(t: A) -> ()
      //执行函数generic,传入的是%16,也就是%2
      call swiftcc void @"$s4main7generic1tyx_tlF"(%swift.opaque* noalias nocapture %16, %swift.type* %17)
    
      /*
        看到这里,结构信息就更加明确了
        首先是一个再抽象的结构 { i8*, %swift_refcounted*}
        其中的%swift_refcounted*存储的是我们开辟的中间层堆空间数据{ %swift.refcounted, %swift.function }
        其中%swift.function就是闭包的数据结构
      */
    
      %.data3 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
      %18 = load %swift.refcounted*, %swift.refcounted** %.data3, align 8
      call void @swift_release(%swift.refcounted* %18) #2
      %19 = bitcast %swift.function* %2 to i8*
      call void @llvm.lifetime.end.p0i8(i64 16, i8* %19)
      ret i32 0
    }
    

    通过上面的IR代码逻辑,还原一下此时的闭包结构体的数据结构是怎样的

    //如果函数中的泛型参数传入的是闭包呢?
    
    //1.再抽象结构体 {i8*, %swift_refcounted*}
    struct Reabstraction<T> {
        //再抽象函数地址
        var ptr: UnsafeRawPointer
        var function: UnsafePointer<MiddleLayer<T>>
    }
    
    //2.中间层{ %swift_refcounted, %swift_function}
    struct MiddleLayer<T> {
        var heapObject: HeapObject
        var closureData: ClosureData<T>
    }
    
    //3.闭包的结构体
    struct ClosureData<T> {
        var ptr: UnsafeRawPointer
        //如果传入的是函数的话,这里为nil。上面的ptr为函数地址
        var captureValue: UnsafePointer<T>
    }
    
    struct Box<T> {
        var heapObject: HeapObject
        var captureValue: T
    }
    
    struct HeapObject {
        var metadata: UnsafeRawPointer
        var refCount: Int
    }
    
    //结构体{i8*, i64(捕获值)}
    func makeIncreament() -> (Int) -> Int {
        var runningTotal = 10
        return {
            runningTotal += $0
            return runningTotal
        }
    }
    
    func generic<T>(t: T) {
        let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
        ptr.initialize(to: t)
        defer {
            ptr.deinitialize(count: 1)
            ptr.deallocate()
        }
    
        let closurePtr = UnsafeRawPointer(ptr).assumingMemoryBound(to: Reabstraction<Box<Int>>.self)
    
        //再抽象闭包函数地址,闭包执行时调用的转发函数地址。先执行它再转发到闭包的函数地址
        print(closurePtr.pointee.ptr) // 0x0000000100003b70
        
        /*
         ❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100003b70
         0000000100003b70 t _$sS2iIegyd_S2iIegnr_TRTA
         
         ❯ xcrun swift-demangle sS2iIegyd_S2iIegnr_TRTA
         $sS2iIegyd_S2iIegnr_TRTA ---> partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@unowned Swift.Int) -> (@unowned Swift.Int) to @escaping @callee_guaranteed (@in_guaranteed Swift.Int) -> (@out Swift.Int)
         */
    
        //闭包的函数地址
        print(closurePtr.pointee.function.pointee.closureData.ptr) // 0x0000000100003c30
        
        /*
         ❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100003c30
         0000000100003c30 t _$s9swiftTest14makeIncreamentS2icyFS2icfU_TA
         
         ❯ xcrun swift-demangle s9swiftTest14makeIncreamentS2icyFS2icfU_TA
         $s9swiftTest14makeIncreamentS2icyFS2icfU_TA ---> partial apply forwarder for closure #1 (Swift.Int) -> Swift.Int in swiftTest.makeIncreament() -> (Swift.Int) -> Swift.Int
         */
    
        //拿到捕获的值
        print(closurePtr.pointee.function.pointee.closureData.captureValue.pointee.captureValue) // 10
    }
    
    var f = makeIncreament()
    
    generic(t: f)
    
    //顺带回忆一下捕获多个值的时候的数据结构
    //struct ClosureData<T> {
    //    var ptr: UnsafeRawPointer
    //    var captureValue: UnsafePointer<T>
    //}
    //
    //struct Box<T1, T2> {
    //    var heapObject: HeapObject
    //    var value: UnsafePointer<Box1<T1>>
    //    var value1: T2
    //}
    //
    //struct Box1<T> {
    //    var heapObject: HeapObject
    //    var value: T
    //}
    

    至此,将闭包表达式传入泛型参数后,此时的数据结构已经还原出来了

    那么我们思考一个问题,引入再抽象层和中间层的用意是什么呢?

    因为我们可能传入的是闭包表达式也有可能传入的是函数。本质上传入函数或闭包表达式时,为了泛型管理统一,抽象了一层中间层来统一管理,捕获我们的函数或闭包表达式

    四.Sequence&Collection

    1.Sequence

    对于Sequence协议来说,表达的既可以是有限的集合,也可以是一个无限的集合,而它只需要提供集合中的元素和如何访问这些元素的接口即可。

    我们在使用AnySequence了解类型擦除的时候,其实已经对它有了一些了解。需要返回一个遵循IteratorProtocol的迭代器。而迭代器负责实现next函数来返回数据,当返回值为nil的时候表示迭代结束(有限的集合),否则会无限循环下去(也就是表示无限的集合)。

    for-in其实也是一个语法糖,其实也是使用迭代器来返回数据。当然这个可以通过SIL代码来观察

    IteratorProtocol源码

    public protocol IteratorProtocol {
      /// The type of element traversed by the iterator.
      associatedtype Element
    
      /// Advances to the next element and returns it, or `nil` if no next element
      /// exists.
      ///
      /// Repeatedly calling this method returns, in order, all the elements of the
      /// underlying sequence. As soon as the sequence has run out of elements, all
      /// subsequent calls return `nil`.
      ///
      /// You must not call this method if any other copy of this iterator has been
      /// advanced with a call to its `next()` method.
      ///
      /// The following example shows how an iterator can be used explicitly to
      /// emulate a `for`-`in` loop. First, retrieve a sequence's iterator, and
      /// then call the iterator's `next()` method until it returns `nil`.
      ///
      ///     let numbers = [2, 3, 5, 7]
      ///     var numbersIterator = numbers.makeIterator()
      ///
      ///     while let num = numbersIterator.next() {
      ///         print(num)
      ///     }
      ///     // Prints "2"
      ///     // Prints "3"
      ///     // Prints "5"
      ///     // Prints "7"
      ///
      /// - Returns: The next element in the underlying sequence, if a next element
      ///   exists; otherwise, `nil`.
      mutating func next() -> Element?
    }
    

    Sequence源码

    public protocol Sequence {
      /// A type representing the sequence's elements.
      associatedtype Element
    
      /// A type that provides the sequence's iteration interface and
      /// encapsulates its iteration state.
      associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
    
      /// A type that represents a subsequence of some of the sequence's elements.
      // associatedtype SubSequence: Sequence = AnySequence<Element>
      //   where Element == SubSequence.Element,
      //         SubSequence.SubSequence == SubSequence
      // typealias SubSequence = AnySequence<Element>
    
      /// Returns an iterator over the elements of this sequence.
      __consuming func makeIterator() -> Iterator
    
      ... 下面的代码省略
    }
    

    使用Sequence创建一个有限的集合

    struct LGIterator: IteratorProtocol {
        
        let sequence: LGSequence
        
        var count = 0
        
        init(_ sequence: LGSequence) {
            self.sequence = sequence
        }
        
        //如果一直不返回nil,就会变成无限集合
        mutating func next() -> Int? {
            count += 1
            if count > sequence.count {
                return nil
            }
            return count
        }
    }
    
    struct LGSequence: Sequence {
        
        var count: Int
        
        func makeIterator() -> LGIterator {
            return LGIterator(self)
        }
    }
    
    let sequence = LGSequence(count: 10)
    
    for element in sequence {
        print(element)
    }
    

    也可以使用IteratorProtocol来表现一个无限的集合

    struct Iterator: IteratorProtocol {
        
        let value: Int
        
        func next() -> Int? {
            return value
        }
    }
    
    let iterator = Iterator(value: 10)
    
    while let value = iterator.next() {
        print(value)
    }
    

    2.Collection

    MutableCollection允许集合通过下标改变自身元素

    protocol MutableCollection : Collection {
      subscript(i: Index) -> Iterator.Element {get set}
    }
    

    RangeReplaceableCollection允许集合修改任意区间的元素

    BidirectionalCollection可以向前或向后遍历集合

    RandomAccessCollection任意访问集合元素。继承自BidirectionalCollection

    3.通过Collection表达一个环形数组

    通过环形数组案例来了解MutableCollectionRangeReplaceableCollectionBidirectionalCollectionRandomAccessCollection

    //官方代码:返回2的下一次幂
    extension FixedWidthInteger {
        /// Returns the next power of two.
        @inlinable
        func nextPowerOf2() -> Self {
            guard self != 0 else {
                return 1
            }
            return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
        }
    }
    
    /*
     环形数组:顾名思义,数组是环形的。也就是甜甜圈形状,并且这个数组永远不会数组越界
     头下标:负责记录取值的,每取一次值,下标值+1。永远指向第一个元素
     尾下标:负责插入值,每插入一次值,下标值+1。永远指向第一个空闲空间的index
     
     当下标超过数组长度后,进行模运算后,又会出现在数组的开头。形成一个闭环
     */
    struct RingBuffer<Element> {
        //ContiguousArray: Swift原生数组,更高效
        //Array: 可以和OC交互的
        var _buffer: ContiguousArray<Element?>
        
        var headIndex: Int = 0
        
        var tailIndex: Int = 0
        
        /*
         例子: objc_msgSend中sel&mask = index
         当一个数对另一个数取模时,例如:x % y。如果y的值是2^n时,此时我们可以使用&运算来代替模运算。
         这中间其实就利用了2进制数的特性来完成的,2^n-1,二进制数n-1位全是1,进行&操作后刚好取到n-1的数据,此时的数据就是模
         例如: x % 2^n = x & 2^n-1,使用&来代替模运算的高额开销,提高性能
         */
        var mask: Int {
            return _buffer.count - 1
        }
        
        init(capacity: Int) {
            self._buffer = ContiguousArray<Element?>(repeating: nil, count: capacity.nextPowerOf2())
        }
        
        mutating func appendValue(_ value: Element) {
            _buffer[tailIndex] = value
            indexAdvance(index: &tailIndex, by: 1)
        }
        
        mutating func read() -> Element? {
            let element = _buffer[headIndex]
            indexAdvance(index: &headIndex, by: 1)
            return element
        }
        
        func indexAdvance( index: inout Int, by: Int) {
            index = (index + by) & mask
        }
    }
    
    //MutableCollection -> 可以通过下标修改集合数据
    extension RingBuffer: Collection, MutableCollection {
        /*
         Collection 必须实现startIndex、endIndex、index()和subscript中的get
         
         MutableCollection 必须实现subscript中的set来通过下标修改集合的元素
         */
        var startIndex: Int {
            return self.headIndex
        }
        
        var endIndex: Int {
            return self.tailIndex
        }
        
        func index(after i: Int) -> Int {
            return (i + 1) & mask
        }
        
        subscript(position: Int) -> Element? {
            get {
                return _buffer[position]
            }
            //MutableCollection
            set {
                _buffer[position] = newValue
            }
        }
    }
    
    //RangeReplaceableCollection -> 允许集合修改任意区间的元素
    extension RingBuffer: RangeReplaceableCollection {
        
        //必须要给一个初始化函数
        init() {
            self.init(capacity: 10)
        }
            
        /*
         已经验证过了,但是init函数没有执行到。具体为什么要实现init函数暂时还不清楚
         */
        mutating func remove(at i: Int) -> Element? {
            var currentIndex = i
            let element = _buffer[i]!
            switch i {
            /*
             如果是头节点,直接将头节点移到下一位,同时将元素置为nil
             */
            case headIndex:
                indexAdvance(index: &headIndex, by: 1)
                _buffer[i] = nil
            default:
                _buffer[i] = nil
                var nextIndex = (i + 1) & mask
                //这里实际上就是执行相邻元素互换,从i开始到tailIndex-1
                while nextIndex != tailIndex {
                    _buffer.swapAt(currentIndex, nextIndex)
                    currentIndex = nextIndex
                    nextIndex = (currentIndex + 1) & mask
                }
                //删除了元素,尾节点向前移动一位
                indexAdvance(index: &tailIndex, by: -1)
            }
            return element
        }
        
        /*
         当然这里还有一些协议函数也可以去实现,例如:func removeFirst() -> Element?
         */
    }
    
    //RangeReplaceableCollection -> 可以向前或向后遍历集合
    extension RingBuffer: BidirectionalCollection {
        //返回下一个元素的下标
        func index(before i: Int) -> Int {
            //这里是向前
            return (i - 1) & mask
        }
    }
    
    //通过字面量数组创建
    extension RingBuffer: ExpressibleByArrayLiteral {
        init(arrayLiteral elements: Element...) {
            self.init(capacity: elements.count)
            for e in elements {
                appendValue(e)
            }
        }
    }
    
    var ringBuffer: RingBuffer = RingBuffer(arrayLiteral: 0, 1, 2, 9, 4, 5)
    
    ringBuffer.appendValue(6)
    
    /*
     此时当我们没有实现RandomAccessCollection时,也是可以使用index(0, offsetBy: 3)函数的
     如果没有实现,那么执行的是编译器的默认实现,如果实现了执行实现的函数方法
     */
    print(ringBuffer.index(0, offsetBy: 3)) // 3
    
    
    /*
     在这里的需求里,如果不实现RandomAccessCollection就会出问题
     比如ringBuffer.index(0, offsetBy: 100)
     */
    //print(ringBuffer.index(0, offsetBy: 100)) // 会报错,已经超出了EndIndex,数组越界
    
    
    //RandomAccessCollection -> 任意访问集合元素
    extension RingBuffer: RandomAccessCollection {
        
        func distance(from start: Int, to end: Int) -> Int {
            return start + end
        }
    
        func index(_ i: Int, offsetBy distance: Int) -> Int {
            return (i + distance) & mask
        }
    }
    
    /*
     如果我们实现了RandomAccessCollection再来实现执行ringBuffer.index(0, offsetBy: 100)
     此时就会走到我们自定义的逻辑,就不会再发生错误了
     */
    print(ringBuffer.index(0, offsetBy: 100)) // 4
    

    五.some关键字

    Swift5.1中引入了some不透明类型来解决协议类型(泛型协议/含有Self)不能作为类型的问题。这个问题,我们在研究泛型的时候已经清楚了,主要是因为内存大小的分配问题。

    //这样编译器就不会报错了,此时的userData就是一个遵守了DataFetch协议的不透明类型
    let userData: some DataFetch = UserData()
    

    一般与协议一起使用,some protocol。表明这个类型是遵循协议的类型,具体类型并不知道。主要的好处就是隐藏了实际类型。

    使用场景:

    • 当我们在涉及底层库的时候,底层类型永远不会暴露给使用者,只能使用静态特性(也就是当前遵循的协议)。相当于把库的内部实现和外部使用分离开。
    • 当我们需要使用泛型协议类型的时候,可以通过some使用。
    //关于some,不透明类型介绍
    
    protocol MyProtocol {
        func test()
    }
    
    struct LGTeacher: MyProtocol {
        func test() {
            print("1")
        }
    }
    
    /*
     此时的some就是不透明类型,遵循MyProtocol协议的类型
     */
    func makeStruct() -> some MyProtocol {
        return LGTeacher()
    }
    
    //let t: some MyProtocol
    let t = makeStruct()
    t.test()
    

    函数中返回值使用some

    //返回不透明类型:Equatable
    func makeA() -> some Equatable {
        "A"
    }
    
    func makeB() -> some Equatable {
        "B"
    }
    
    let a = makeA()
    let a1 = makeA()
    
    print(a == a1) // true
    
    let b = makeB()
    let b1 = makeB()
    
    //此时的a和b就不是同一个类型了,编译器会认为是两个不透明类型不相等
    //print(a == b) // Binary operator '==' cannot be applied to operands of type 'some Equatable' (result of 'makeA()') and 'some Equatable' (result of 'makeB()')
    

    相关文章

      网友评论

        本文标题:Swift -- 10.泛型&集合

        本文链接:https://www.haomeiwen.com/subject/wnuwlrtx.html