Swift 协议与泛型的实现

概念简述

数据结构

Swift的数据结构可以大体拆分为:ClassStructEnum

内存分配(Memory Allocation)

每一个进程都有独立的进程giti空间,进程空间中能够用于索引超出了数组界限什么意思内存分配的区域主要分为git命令两种:

  • 栈区(Stack)
  • 堆区(Heap)
  • 对象的内存分配

这里需要借助一下swift编译过程中产生的中间语言 SIgithubLSwift Intermediate Language

Swift编程语言是在LLVM上构建,并且使用LLVM IR和LL数组初始化VM的后索引端去生成代码。但是S索引超出矩阵维度wift编译器还包含新的高级别的中间语言,称为SILSIL会对Swift进行较高级别的语义分析和优化。

Swift 协议与泛型的实现

//main.swift
class Person {
    var name: String?
    func doSometing() { }
}
let person: Person = Person()
person.doSometing()
let type: Person.Type = Person.self

命令 swiftc -emit-sil

Swift 协议与泛型的实现

class_method 表示类方法的动态分派是通过sil_vtable来实现。

viewController.swift
class Person {
    var name: String?
    func doSometing() { }
}
class ViewController: UIViewController {
    var person: Person? //在堆区分配内存
    override func viewDidLoad() {
        super.viewDidLoad()
        person = Person()
        person?.doSometing()
    }
}

Swift 协议与泛型的实现

Swift 协议与泛型的实现

Type Met指针数组和数组指针的区别adata

The Swift runtime keeps a metadata recordandroid平板电脑价格 for every type used in a program, including every instantiation of generic types.

github.com/apple/swift…

Swift运行giti时为程序中使用的每种类型保存元数据记录,指针包括泛型类型的每个实例化。

内存中的存储结构

Swift 协议与泛型的实现

对象的前8个字节指向类型信息,也就是指向元类型(metadata)。所以这8个字节就是一个元类型指针

Swift 协议与泛型的实现

Swift 协议与泛型的实现

方法分派方式

一个方法会指针c语言在运行时被调用或者是一个方法被唤起,是因为编译器有一个计算机制,用来选择正确的方法,然后通过传递参数来唤起它,这个机制通git教程常被成为分派(dispatch),分派就是处理方法调用的过程。方法从书写完成到调用完成,概括上会经历编译期和运行期两个阶段,确定哪个方法被执行,也是在这两个时期进行的。故选择正确方法的阶段,可以分为编译期和运行期,而数组去重分派机制通过这两个不同的时期分为两种:

  • 静态分派(static dispatch)
  • 动态分派(dynamic dispatch)

能够在编译期确定执行方法的方式叫做静态分派,无法在编译期确定,只能在运行时去确定执行方法的分派方式叫做动态分派。

Static dispatch更快,而且静态分派可以进行内联等进一步的优化,使得执行更快速,性能更高。

但是对于多态的情况,我们不能在编译期确定最终的类型,这里就用到了Dynamgitlabic dispatch动态分派。动态分派的实现是数组c语言,每种类型都会创建一张表数组去重,表内是一个包含了方法指针的数组。动态分派更灵活,但是因为有查表和跳转的操作,并且因为很多特点对于指针说漫编译器来说并不明确,所以相当于block了编译器的数组和链表的区别一些后期优化。所以速度慢于Static dispatch

bugs.swift.org/browse/SR-5…

组件关系

在swift中组件关系可以分为inheritanceproto数组初始化colsgenerics

  • inheritance 继承
  • protoc数组排序ols 协议
  • generics 泛型

多态(特性)

指允许不同对象对同指针数组一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。

Swift 协议与泛型的实现

问题:

  • Protocol Type 和 Generic Type 如何实现存储?
  • Protocol Type 和 Generic Type 如何拷贝变量?
  • Protocol Type 和 Generic Type 如何进行方法派发?

协议类型 Proto索引超出了数组界限什么意思col Type

例子,这里是通过Protocol指针式万用表使用方法 Type实现多态,几个类之间没有继承关系

protocol Person {
    func doSometing()
}
struct Person1: Person {
    var x, y: Double
    func doSometing() {}
}
struct Person2: Person {
    var x1, y1, x2, y2: Double
    func doSometing() {}
}
//遵守了Person协议的类型集合,可能是Person1或者Person2
var persons: [Person] = [Person1(x: 1.0, y: 1.0), Person2(x1: 1.0, y1: 1.0, x2: 1.0, y2: 1.0)]
for p in persons {
    p.doSometing()
}

Swift 协议与泛型的实现

init_existentgitiial_addr %x 初始化由%x 引用git命令的内存为Existential Container。

可以发现在这种情况下,变量 persons 中存储的元数组去重素是一种特殊的数据类型:E指针式万用表xis数组tential Container

Existential Cont索引失效的几种情况ainer

Existential Container 是编译器生成Git的一种特殊的数据类型,用于管理遵指针数组和数组指针的区别守了相同协议的协议类型。因为这些数据类型的内存空间尺寸不同,使用 Extential Container 进行管理可以实github永久回家地址现存储一致性。

在这里我们可以使用Memlayout这个API去验证一下(本机内存对齐是 8 字节数组

Swift 协议与泛型的实现

可见 Exis指针是什么tential Container 类型占索引符号用了 5 个内存单元(也称 ,Word)。其结构如下图所示:

Swift 协议与泛型的实现

  • 三个词作数组的定义Value Buffer
  • 一个词作为 Value Witness Table索引
  • 一个词作为 Protocol Witness Table 的索引。

Value Buffer

Value Buffer 占据 3 个词,存储的可能是值,也可能是指针。对于 Small Value(存储空间小于等于 Value Buffer),可以直接内联存储在 Value Buffer 中。对指针c语言于 Large Value(存储空间大于 Value Buffer),则会在堆区分配内存进行存储,Value Buffer 只存储对应的指针git命令

Value Witness Table

由于协议类型的具体类型不同,其内存布局也不同,Value Witness数组和链表的区别 Table 则是对索引的作用协议索引是什么意思数组的定义型的生命周期进行专项管理,从而处理具体类型的初始化、拷贝、销毁。索引超出矩阵维度

Protocol Witness Table

Value Wit索引失效的几种情况ness Table 管理协议类型的生命周期,Protandroid是什么系统ocol Witnes数组指针s Table 则管理协议类型的方法调用。

在 OgitlabOP 中,基于继承关系的多态是通过 Virtual Tabl数组的定义e 实现的;在 POP 中,没有继承关系,因为无法使用 Virtual Table 实现基于协议的多态,取而代之的是 Protocol Witness Table。gitlab

拷贝变量

上面的描述中我们可以了解到

  • 对于 Small Vgitlabalue,直接内联存储在 Existential Contain索引页是哪一页er 的 Value Buffer 中。
  • 对于 Large Value,通过堆区分配进行存储,使用 Existential Containter 的 Value BufferAndroid 进行索引。

Indirect Storage With Copy-指针万用表怎么读数On-Write

原理:优先使用内存github指针,拷贝时仅仅拷贝 Existential Container,当修改值时,先检测引用计数,如果引用计数大于 1,则开辟新的堆区内存。

泛型类型 Generic Type

由Protocol Type实现的多态是动态的多态(Dynamic Polymorphism),那什么是静gitlab态多态呢

例子

protocol Person {
    func doSometing()
}
struct Person1: Person {
    var x, y: Double
    func doSometing() {}
}
struct Person2: Person {
    var x1, y1, x2, y2: Double
    func doSometing() {}
}
func todo(person: Person) {
    person.doSometing()
}
let person1 = Person1(x: 1.0, y: 1.0)
todo(person: person1)

这个时候通过SIL文件我们可以看到这里还是使用Existential Container数据结构,但是上面我们说得使用Existential Container是为了管理遵守了相同协议的协议类型以及指针数组和数组指针的区别内存对齐,从上面的代码上看这个时候编译器是知道传进去的是Person1类型的对象,故这个时候我们可以使用泛型Generic code来实现。

Swift 协议与泛型的实现

func todo<T: Person>(person: T) {
    person.doSometing()
}

Swift 协议与泛型的实现

故我们可以根据方法调用时数据类型是否确定可以将多态分为:静态多态(Static Polymorphism)和 动态多态(Dynamic Polymorphism)。

泛型和android是什么系统Protocol Type的区别在于:

  • 泛型支持的是静态android手机多态。
  • 每个调用上指针数组和数组指针的区别下文只有一种类型。
  • 在调用链中会通过类型降级进行类型取代。

Value Buffer

Value Buffer 占据 3 个词,存储的可能是值,也可能是指针。对于 Small Value(存储空间android下载小于等于 Value Buffer),可以直接github永久回家地址内联存储在 Value Buffer 中。对于 Large Value(存储空间大于 Value B索引页是哪一页uffer),则会在堆区分配内存进行存储,Value指针说漫 Buffer 只存储对应的指针。

Vgit教程alue Witness Table

由于协议类型的具体类型不同,其内存布局也不同,Value W数组指针itness Table 则是对协议类型的数组排序生命周期进行专项管理,从而处理具体类型的数组公式初始化、拷贝、销毁。

Protocol Witness Table

Value Witness Table 管理协议类型的生命周期,Protocol Witnes指针万用表读数图解s Table 则管理协议类型的方法调用。

在 OOP 中,基于继承关系的多态是通过 Virtual Table 实现的;在指针数组和数组指针的区别 POP 中,没有继承关系,因为无法使用 Virtual Table 实现基于协议的多态,取而代之的是 Protocol Witness Table。

拷贝变量

上面的描述中我们可以了解到

  • 对于 Small Value,直接内联存储在 Existential Container 的 Value Buffer 中。
  • 对于 Large Value,通过堆索引页是哪一页区分配进行存储,使用 Existential Containandroid手机ter 的 Value Buffer 进行索数组公式引。

Indirect Storage With Copy-On-Write

原理:优先使用内存android下载安装指针,拷贝时仅仅拷贝索引超出了数组界限什么意思 Existential Con指针tainer,当修改值时,先检测引用计数,如果引用计数大于 1,则开辟新的堆区内存。

泛型类型 Generic Type

由Prot索引o索引是什么意思col Type索引实现的多态是动态的多态(Dynamic Polymorphism),那什么是静态多态呢

例子

protocol Person {
    func doSometing()
}
struct Person1: Person {
    var x, y: Double
    func doSometing() {}
}
struct Person2: Person {
    var x1, y1, x2, y2: Double
    func doSometing() {}
}
func todo(person: Person) {
    person.doSometing()
}
let person1 = Person1(x: 1.0, y: 1.0)
todo(person: person1)

这个时候通过SIL文件我们可以看到这里还是使用Existential Container数据结构,但是上面我们说得使用Existential Container是为了管理遵守了相同协议的协议类型以及内存对齐,从上面的代码上看这个时候编译器是知道传进去的是Person1类型的对象,故这个时候我们可以使用泛型Generic code来实现。

Swift 协议与泛型的实现

func todo<T: Person>(person: T) {
    person.doSometing()
}

Swift 协议与泛型的实现

故我们可以根据方法调用时数指针数组据类型是索引超出了数组界限什么意思否确定可以将多态分为:静态多态(Static Pandroid是什么系统o指针是什么lymorphism)和 动态多态(Dynamic Polymorphism)。

gitee型和Protocol Type的区别在于:

  • 泛型支持的是静态多态。
  • 每个调用上下文只有索引失效一种类型。
  • 在调用链中会通过类型降级进行类型取代。

方法调索引页是哪一页

在方法执行时,Swift将泛型T绑定为调用方使用的具体类型

todo(person: person1) –>todo<T = Person1>(person: person1)

泛型方法调用的具体实现为:

  • 同一种类型的任何实例,都共享同样的实现,即使用同一个Protoc指针ol Witness Tab指针万用表怎么读数le。

使用Protocol/Value Wandroid下载itness Table。

  • 每个调用上下文只有一种类型:这里没有使用Existential Container, 而是将Protocol/Val指针式万用表使用方法ue Witness Table作为调用方的额外参数进行传递。
  • 变量初始化和方法调用,都使用传入的VWTPWT来执行。

泛型特化

类型降级后,产生特定类型的方法,为泛型的每个类型创造对应的方法静态多态下进行特定优化数组的定义 虚函数调用替换为调用函数映射。

例如

func todo<T: Person>(person: T) {
    person.doSometing()
}
todo(person: person1)
转换成
func todo<Person1>(person: Person1) {
    person.doSometing()
}

因为是静态多态。所以可以进行很强大的优化,比如进数组的定义行内联实现,并且通过获取上下文来进行更进一步的优化。从而降低方法数量。优化后可以更精确和具体。

参考资料

llvm.org/devmtg/2015…

github.com/apple/swift…

github.com/apple/swift…

www.jianshu.com/p/c2880460c…

www.rightpoint.com/rplabs/swit…

airspeedvelocity.net/2015/03/26/…

developandroid是什么手机牌子er.apple.com/documentati…

我们是字节深圳飞书团队,致力于打造高性能、卓越体验的企业协同办公软件,感兴趣的指针数学童鞋可以投递简历到zenghao.howie@bytedance.com。 iOS 职位:https://job.toutiao.com/s/Nj3oTgithub下载KV Android 职位:https://job.toutiao.com/s/NjTyGkf

发表评论

提供最优质的资源集合

立即查看 了解详情