Swift底层进阶--014:泛型

  • 泛型是为Swift编程提供灵活性的一种语法,可以提升代码的复用性和抽象能力
  • 例如:SwiftArrayDictionary类型都是泛型集合
  • 泛型在函数、枚举、结构体、类中都能得到充分应用,它的引入可以起到占位符的作用,当类型暂时不确定时,只有等到调用函数时才能确定具体类型的时候可以引入泛型
func swap<T>(_ a: inout T, _ b: inout T) {
    let tmp = a
    a = b
    b = tmp
}

例如交换两个值,使用泛型,可以无视参数类型,提升方法的复用性和抽象能力

类型约束

在⼀个类型参数后⾯放置协议或者类,⽐如要求类型参数T遵循Equatable协议

func test<T: Equatable>(_ a: T ,_ b: T) -> Bool{
    return a == b
}

例如未遵循Equatable协议的结构体类型,将实例对象传入方法,编译报错

编译报错

func test<T: Equatable>(_ a: T ,_ b: T) -> Bool{
    return a == b
}

struct LGTeacher: Equatable {
    var age: Int = 18
}

var t1 = LGTeacher()
var t2 = LGTeacher()

var isTest = test(t1, t2)

只有遵循Equatable协议的类型可以使用方法

关联类型

在定义协议的时候,使⽤关联类型给协议中⽤到的类型起⼀个占位符名称

protocol StackProtocol {
    associatedtype Item
}

struct LGStack: StackProtocol {
    
    typealias Item = Int
    
    private var items = [Item]()
    
    mutating func push(_ item: Item){
        items.append(item)
    }

    mutating func pop() -> Item?{
        
        if items.isEmpty { return nil }
        
        return items.removeLast()
    }
}

上述代码,StackProtocol协议中使用associatedtype关联类型定义的Item,相当于占位符。在LGStack中使用,必须先指定Item的实际类型,使用typealias关键字

结构体内指定Int类型显然不够灵活,这里使用泛型进一步修改:

protocol StackProtocol {
    associatedtype Item
}

struct LGStack<T>: StackProtocol {
    
    typealias Item = T
    
    private var items = [Item]()
    
    mutating func push(_ item: Item){
        items.append(item)
    }

    mutating func pop() -> Item?{
        
        if items.isEmpty { return nil }
        
        return items.removeLast()
    }
}

var t = LGStack<Int>()
Where语句
protocol StackProtocol {
    associatedtype Item
    var itemCount: Int{ get }
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}

struct LGStack<T>: StackProtocol{
    typealias Item = T
    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]
    }
}

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
}
        
var s1 = LGStack<Int>()
var s2 = LGStack<Int>()

var isTest = compare(s1, s2)

上述代码,where语句要求T1.ItemT2.Item必须类型相同,且T1.Item必须遵循Equatable协议,这意味着T2.Item也必须遵循Equatable协议

where语句也可以在扩展中使用

extension LGStack where Item: Equatable{}

如果希望在当前泛型制定类型的时候拥有特定功能,可以使用这种写法:

protocol StackProtocol {
    associatedtype Item
}

extension LGStack where Item == Int {
    func test(){
        print("Item == Int")
    }
}

struct LGStack<T>: StackProtocol{
    typealias Item = T
}

var stack = LGStack<Int>()
stack.test()

上述代码,如果指定的ItemInt类型,将无法找到test方法,编译报错

编译报错

泛型函数
func testGenric<T>(_ value: T) -> T {
    let tmp = value
    return tmp
}

class LGTeacher {
    
}

var t = LGTeacher()

testGenric(10)
testGenric(t)

上述代码,test方法可以接收任何类型的参数,将value赋值给tmp,地址有可能在堆上,也有可能在栈上,那么系统是如何进行开辟空间和内存对齐的呢?

将上述代码生成IR文件:swiftc -emit-ir main.swift | xcrun swift-demangle

IR

所以泛型是通过ValueWitnessTable来进⾏内存操作,ValueWitnessTable就是VWT值目击表

通过源码对VWT值目击表进行分析:

ValueWitnessTable

ValueWitnessTable也是一个结构体,里面存储了sizealignmentstridecopydestory等信息。对于每一种数据类型,里面都存储了各自的VWT,目的是用于管理类型的大小步长、对⻬⽅式、创建、销毁、复制、是否需要引用计数

找到NativeBox

NativeBox

NativeBox用于值类型,例如Int类型,系统会询问Metadata中的VWT,获取到sizestride进行内存空间的分配。然后调用VWTcopy方法拷贝值,将结果返回,返回后销毁局部变量

找到RetainableBoxBase

RetainableBoxBase

RetainableBoxBase用于引用类型,同样是通过Metadata中的VWT进行一系列的操作

  • 泛型类型使⽤VWT进⾏内存管理,VWT由编译器⽣成,其存储了该类型的sizealigment(对⻬⽅式)以及针对该类型的基本内存操作
  • 当对泛型类型进⾏内存操作(如:内存拷⻉)时,最终会调⽤对应泛型类型的VWT中的基本内存操作。泛型类型不同,其对应的VWT也不同
  • 对于⼀个值类型,例如:Int:该类型的copymove操作会进⾏内存拷⻉。destroy操作则不进⾏任何操作。
  • 对于⼀个引⽤类型,例如:class:该类型的copy操作会对引⽤计数+1move操作会拷⻉指针,⽽不会更新引⽤计数。destroy操作会对引⽤计数-1
泛型的⽅法调⽤
func makeIncrement() -> (Int) -> Int{
    var  runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func testGen<T>(_ value: T) {

}

let makeInc = makeIncrement()
testGen(makeInc)

上述代码,将⼀个函数当做泛型传递,函数也是一个结构体,那传给泛型的会是一个结构体吗?

将上述代码生成IR文件:swiftc -emit-ir main.swift | xcrun swift-demangle

IR

使用Swift代码还原上述结论

struct HeapObject{
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}

struct FuntionData<T>{
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<T>?
}

struct Box<T> {
    var refCounted: HeapObject
    var value: T
}

struct GenData<T>{
    var ref: HeapObject
    var function: FuntionData<T>
}

func makeIncrement() -> (Int) -> Int{
    var  runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func testGen<T>(_ value: T){
    let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: value)

    let ctx = ptr.withMemoryRebound(to: FuntionData<GenData<Box<Int>>>.self, capacity: 1) {
        $0.pointee.captureValue?.pointee.function.captureValue!
    }

    print(ctx?.pointee.value)
}

let makeInc = makeIncrement()
testGen(makeInc)

//输出以下内容:
Optional(10)

GenData相当于进行了一层包装,目的是解决不同值类型之间传递的问题

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

推荐阅读更多精彩内容

  • Swift 进阶之路 文章汇总[https://www.jianshu.com/p/5fbedf309237] 本...
    Style_月月阅读 884评论 1 3
  • 协议为方法、属性、以及其他特定的任务需求或功能定义蓝图。协议可被类、结构体、或枚举类型采纳以提供所需功能的具体实现...
    HotPotCat阅读 1,500评论 2 5
  • 本章将会介绍 泛型所解决的问题泛型函数类型参数命名类型参数泛型类型扩展一个泛型类型类型约束关联类型泛型 Where...
    寒桥阅读 631评论 0 2
  • 泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。 你可避免编写重复的代码,而是用一...
    DevXue阅读 164评论 0 0
  • 泛型(Generics) 泛型代码允许你定义适用于任何类型的,符合你设置的要求的,灵活且可重用的 函数和类型。泛型...
    果啤阅读 668评论 0 0