Swift -- 10.泛型&集合

一.泛型语法

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()')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容