一、协议的基本语法
1. 协议的界说
协议能够用来界说办法、特点、下标的声明,协议能够被枚举、结构体、类恪守(多个协议之间用逗号隔开)。
// 协议界说办法、特点、下标
protocol Drawable {
func draw()
var x: Int { get set }
var y: Int { get }
subscript(index: Int) -> Int { get}
}
protocol Protocol1 {}
protocol Protocol2 {}
protocol Protocol3 {}
// 恪守协议
class Person: Protocol1, Protocol2, Protocol3 {}
协议中界说办法时不能有默许参数值,且默许状况下,协议中界说的内容有必要全部都完成。假如咱们不想强制让遵循协议的类型完成,能够使用 optional 作为前缀放在协议的界说,并且 protocol 和 optional 前要加上 @objc。
@objc protocol Incrementable {
@objc optional func increment(by: Int)
}
2. 协议中的特点
-
协议中界说特点时有必要用var关键字。
-
完成协议时的特点权限要不小于协议中界说的特点权限。协议界说 get、set,用 var 存储特点或 get、set 计算特点去完成,协议界说 get,用任何特点都能够完成。
例如:
protocol Drawable {
func draw()
var x: Int { get set }
var y: Int { get }
subscript(index: Int) -> Int { get}
}
class Person: Drawable {
func draw() {
print("Person draw")
}
var x: Int = 0
var y: Int = 0
subscript(index: Int) -> Int {
set{}
get{ index }
}
}
3. 协议中的 static、class、mutating 和 init
- static 为了确保通用,协议中有必要用 static 界说类型办法、类型特点、类型下标。
protocol Drawable {
static func draw()
}
class Person: Drawable {
// class func draw
static func draw() {
print("Person draw")
}
}
Person.draw() // Person draw
- mutating 只要将协议中的实例办法标记为 mutating,才答应结构体、枚举的具体完成修正本身内存。类在完成办法时不用加 mutating,枚举、结构体才需求加 mutating。
protocol Drawable {
mutating func draw()
}
class Person: Drawable {
func draw() {
print("Person draw")
}
}
struct Point: Drawable {
mutating func draw() {
print("Point draw")
}
}
- init 协议中还能够界说初始化器 init,非 final 类完成时有必要加上 required。
final class Size: Drawable {
init(x: Int, y: Int) { }
}
class Point: Drawable {
required init(x: Int, y: Int) { }
}
假如从协议完成的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化有必要一起加required、override。
protocol Livable {
init(name: String)
}
class Person {
init(name: String) {}
}
class Student: Person, Livable {
required override init(name: String) {
super.init(name: name)
}
}
协议中界说的 init?、init!,能够用 init、init?、init! 去完成,协议中界说的 init,能够用 init、init! 去完成。
protocol Livable {
init()
init?(age:Int)
init!(no:Int)
}
class Person: Livable {
required init() {}
// required init!() {}
required init?(age: Int) {}
// required init(age: Int) {}
// required init!(age: Int) {}
required init!(no: Int) {}
// required init(no: Int) {}
// required init?(no: Int) {}
}
4. 协议中的承继和组合
一个协议能够承继其他协议。例如:
protocol Runnable {
func run()
}
protocol Livable: Runnable {
func breath()
}
class Person: Livable {
func breath() {}
func run() {}
}
协议组合,能够包括1个类类型(最多1个)。咱们来看下面的比如:
// 接纳 Person 或许其子类的实例
func fn0(obj: Person) {}
// 接纳恪守 Livable 协议的实例
func fn1(obj: Livable) {}
// 接纳一起恪守 Livable、Runnable 协议的实例
func fn2(obj: Livable & Runnable) {}
// 接纳一起恪守 Livable、Runnable 协议、并且是 Person 或许其子类的实例
func fn3(obj: Person & Livable & Runnable) {}
fn3 中,参数 obj 协议组合的声明太长了,咱们能够用 typealias 给协议组合取别号,例如:
typealias RealPerson = Person & Livable & Runnable
// 接纳一起恪守 Livable、Runnable 协议、并且是 Person 或许其子类的实例
func fn3(obj: RealPerson) {}
5. CaseIterable 和 CustomStringConvertible
- 让枚举恪守 CaseIterable 协议,能够完成遍历枚举值。
enum Season: CaseIterable {
case spring, summer, autumn, winter
}
let seasons = Season.allCases
print(seasons.count) // 4
for season in seasons {
print(season)
} // spring summer autumn winter
- 恪守 CustomStringConvertible、CustomDebugStringConvertible 协议,都能够自界说实例的打印字符串。
class Person: CustomStringConvertible, CustomDebugStringConvertible {
var age = 0
var description: String{ "person_\(age)" }
var debugDescription: String{ "debug_person_\(age)" }
}
var person = Person()
print(person) // person_0
debugPrint(person) // debug_person_0
-
print 调用的是 CustomStringConvertible 协议的 description。
-
debugPrint、po 调用的是 CustomDebugStringConvertible 协议的 debugDescription。
6. 类专用协议
在协议后边写上 :AnyObject 代表只要类能恪守这个协议,在协议后边写上 :class 也代表只要类能恪守这个协议。
protocol MyProtocol: AnyObject {}
protocol MyProtocol: class {}
二、witness_table
witness_table 翻译过来叫做见证表,它是用来干什么,咱们接下来对它进行一个开端的认识。
在《办法》这篇文章中咱们知道,类的办法的调度是经过虚函数表(VTable)查找到对应的函数进行调用的,而结构体的办法直接便是拿到函数的地址进行调用。那么协议中声明的办法呢,假如类或许结构体恪守这个协议,然后完成协议办法,它是怎么去查找函数的地址进行调用的呢。
1. witness_table 的引入
咱们先声明一份协议 Born,里边有一个 born(:) 办法。类 – Person 恪守 Born 并完成 born(:) 办法,代码如下:
protocol Born {
func born(_ nickname: String)
}
class Person: Born {
var nickname: String?
func born(_ nickname: String) {
self.nickname = nickname
}
}
let p = Person()
p.born("Coder_张三")
接下来咱们把当时的 main.swift 文件编译成 main.sil 文件,经过 sil 代码来观察是否有 VTable。编译完成后找到 main 函数,查看 born(:) 办法的调用,如图:
留意看,born(:) 的类型在 sil 中是 class_method 类型的,在 SIL参阅文档 中有介绍,class_method 类型的办法是经过 VTable 查找的,如图:
接下来咱们看到 main.sil 文件最底部的代码,如图:
能够看到,born(:) 确实是存储在 VTable 当中了,可是下面的 witness_table 是用来干啥的,并且里边也有一个 born(:),这是个啥。接下来我干一件事,我把变量 p 声明为 Born 协议,代码如下:
let p: Born = Person()
接下来重新将 main.swift 文件编译成 main.sil 文件,然后直接看 main 函数,如图:
此刻,咱们发现函数的类型变了,变成了 witness_method 类型的,咱们来看 SIL参阅文档 中是怎么介绍 witness_method 的:
翻译如下:
查找受该协议束缚的泛型类型变量的协议办法的完成。结果将在原始协议的 Self 原型上是通用的,并具有 witness_method 调用约好。假如引证的协议是 @objc 协议,则结果类型具有 objc 调用约好。
啥意思呢,咱们大局查找 @protocol witness for main.Born.born(Swift.String) -> (),找到它的完成,如图:
留意看,它最终仍是会去查找恪守它的类中的 VTable 进行办法的调度。咱们两次的测验唯一的区别在于是否指定变量的类型为 Born 的协议类型,也能够理解为这个调用的办法和我这个变量指定的静态类型有关。
总结如下:
-
假如实例对象的静态类型便是确定的类型,那么这个协议办法经过 VTalbel 进行调度。
-
假如实例对象的静态类型是协议类型,那么这个协议办法经过 witness_table 中对应的协议办法,然后经过协议办法去查找恪守协议的类的 VTable 进行调度。
2. 结构体的 witness_table
知道类的 witness_table 调度状况了之后,咱们来看一下结构体的 witness_table,仍是老办法,经过 sil 代码剖析,代码如下:
protocol Born {
func born(_ nickname: String)
}
struct Person: Born {
var nickname: String?
func born(_ nickname: String) {
self.nickname = nickname
}
}
let p: Born = Person()
p.born("Coder_张三")
接下来重新将 main.swift 文件编译成 main.sil 文件,然后直接看 main 函数,如图:
咱们再来看一下汇编代码,如图:
能够看到,结构体调用协议办法的办法直接便是函数地址调用。当我指定这个变量的类型为 Born 协议的时分,sil main 函数的完成如下:
留意看,这个时分它的类型变成了 witness_method ,咱们再来看这个办法对应的 witness_method 的完成,如图:
能够看到,它最终仍是找到了结构体 born(:) 办法的地址直接进行调用。那这个便是结构体 witness_method 的调用状况。
3. 在协议的 extention 供给协议办法的默许完成
假如对一个协议进行一个 extension,并且完成协议的办法。一起,恪守这个协议的类也完成这个协议办法。那么,经过这个类调用协议办法的时分,调用的是类中完成的协议办法。
代码如下:
protocol Born {
func born(_ nickname: String)
}
extension Born {
func born(_ nickname: String) {
print("Born born(:)")
}
}
class Person: Born {
func born(_ nickname: String) {
print("Person born(:)")
}
}
let p = Person()
p.born("Coder_张三") // Person born(:)
假如在协议中没有声明这个协议办法,可是在协议的 extension 完成了,恪守这个协议的类也完成了这个办法。那么,经过这个类调用这个协议办法的时分,调用的仍是类中完成的办法,可是假如指定了这个变量的类型是协议类型,调用的便是协议的 extension 中完成的办法。
代码如下:
protocol Born {}
extension Born {
func born(_ nickname: String) {
print("Born born(:)")
}
}
class Person: Born {
func born(_ nickname: String) {
print("Person born(:)")
}
}
let p: Born = Person()
p.born("Coder_张三") // Born born(:)
那其实关于第一种状况来讲,这个协议办法的调用流程是和第 1 点中验证的流程结果是相同的,想验证的靓仔能够自己编译成 sil 代码去验证比照。咱们接下来主要看第二种状况,咱们直接看 main 函数和 sil_witness_table,如图:
能够看到,针关于第二种状况,它直接便是拿到 extension 中的函数地址进行调用,并且 sil_witness_table 中没有任何办法。
需求留意的是,这个时分咱们指定了 p 变量的类型为协议类型,但其实就算指定变量 p 的类型为 Person,sil_witness_table 中仍是没有任何办法,这个感兴趣的靓仔能够去尝试,这儿就不一一贴图了,比较费事。
那这儿咱们来做一个总结:
-
首先 sil_witness_table 有没有办法取决于在协议中有没有声明协议办法。
-
假如 sil_witness_table 中没有办法,那么恪守这份协议的类型该 VTable 调度就 VTable 调度,该直接函数地址调用就直接函数地址调用。
-
假如 sil_witness_table 中有办法,那么是否经过 witness_method 去调用取决于当时实例的静态类型是否是协议类型。假如不是,该怎么调度就怎么调度。假如是,那么就经过 witness_method 进行办法的调度。
总的来说当 sil_witness_table 中有办法并且经过 witness_method 调用的时分,无非便是多了一层函数调用。
4. sil_witness_table 在承继关系的状况
-
当一份协议被多个类恪守的时分,那么在各自类中都会有一个 sil_witness_table。
-
当一个类恪守多份协议的时分,那么在这个类中,都有一个每份协议对应的 sil_witness_table,也便是会有很多个 sil_witness_table,这个取决于协议的数量。
-
假如一个类恪守了一份协议,这个类必定会有一个 sil_witness_table,那么这个类的子类和父类是共用一份 sil_witness_table 的。
以上这三点都是能够经过 sil 的代码进行验证比照的,感兴趣的靓仔能够自己试着验证。这儿就不贴图了,比较费事。
三、witness_table 内存布局和内存结构
1. witness_table 在内存中的位置
咱们接下来看一段比较有意思的代码,如下:
protocol Shape {
var area: Double { get }
}
class Circle: Shape {
var radius: Double
init(_ radius: Double) {
self.radius = radius
}
var area: Double {
get {
return radius * radius * 3.14
}
}
}
print("Circle size: \(MemoryLayout<Circle>.size)") // Circle size: 8
print("Shape size: \(MemoryLayout<Shape>.size)") // Shape size: 40
咱们经过 MemoryLayout 获取类型的 Size 的时分,发现协议类型和类类型的 size 不一致,类类型的 size 等于 8 这是正常的,由于类的内存在堆空间,这个 8 仅仅只是一个指针类型的巨细,要想拿到类真实的巨细得经过 class_getInstanceSize 函数。
这个协议类型的 size 等于 40 又是怎么回事呢,咱们接下来在测验一段代码,如下:
let c1: Circle = Circle(10)
let c2: Shape = Circle(20)
print("c1 size: \(MemoryLayout.size(ofValue: c1))") // c1 size: 8
print("c2 size: \(MemoryLayout.size(ofValue: c2))") // c2 size: 40
咱们发现,同样是 Circle 的实例,可是当实例指定为协议类型的时分,这个实例的 size 就变成了 40。这个时分,代表着 c1 和 c2 的内存结构不一致了。
那关于 c1 变量的内存地址咱们应该知道,c1 存储的是它堆空间实例对象的地址,咱们来看一下它的内存布局,如图:
这个便是 c1 的内存布局,并且咱们经过 expr -f float -- <内存地址>
表达式打印出了 radius 的值。
咱们接下来看 c2 的内存布局,如图:
留意看:
-
第一个 8 字节的内存存储的依然是堆空间的地址值。
-
第二个和第三个 8 字节存储的是啥咱们也不知道是什么。
-
第四个 8 字节存储的是堆空间 metadata 的地址。
-
最终的 8 字节存储的其实是 witness_table 的地址。
那怎么知道最终的 8 字节存储的便是 witness_table 的地址呢?最终的 8 字节内存地址为 0x0000000100004028,咱们接下来翻开汇编调试,找到 c2 的创立后找到 witness_table 相关的代码,如图:
如图所示,所以最终的 8 字节存储的其实是 witness_table 的地址。经过以上的剖析,就能够得出 c2 这个类型变量的大致结构,代码如下:
struct ProtoclInstaceStruct {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeRawPointer
}
2. witness_table 的内存结构
经过第 1 点咱们现已知道了 witness_table 在内存中存储的位置,那这个 witness_table 的内存结构是怎么样的呢,这个时分就能够经过 IR 代码去进行剖析了。IR 的语法和怎么编译成 IR 代码在《闭包及其实质剖析》和《办法》这两篇文章中有介绍。
接下来咱们就直接将当时的 main.swift 文件编译成 main.ll 文件,代码仍是第一点的代码,只不过为了避免搅扰我把 c1 变量和 print 打印注释了。编译成 main.ll 文件后咱们直接看 main 函数,代码如下:
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
// 获取 Circle 的 metadata
%3 = call swiftcc %swift.metadata_response @"type metadata accessor for main.Circle"(i64 0) #7
%4 = extractvalue %swift.metadata_response %3, 0
// %swift.type = type { i64 }
// %swift.refcounted = type { %swift.type*, i64 }
// %T4main6CircleC = type <{ %swift.refcounted, %TSd }>
// 创立 Circle 的实例,此刻这个实例的结构为:{ %swift.refcounted, %TSd }
%5 = call swiftcc %T4main6CircleC* @"main.Circle.__allocating_init(Swift.Double) -> main.Circle"(double 2.000000e+01, %swift.type* swiftself %4)
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** },%T4main5ShapeP 实质上是一个结构体
// 留意看,getelementptr为获取结构体成员,i32 0 结构体的内存地址,拿到这个结构体后将 %4 存储到这个结构体的第二个成员变量上
// 也便是将 metadata 存储到这个结构体的第二个成员变量上,此刻这个结构体的结构为:{ [24 x i8], metadata, i8** }
store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.c2 : main.Shape", i32 0, i32 1), align 8
// 这一行在获取 witness table,然后将 witness table 存储到 %T4main5ShapeP 这个结构体的第三个成员变量上(由于取的是 i32 2)
// 此刻 %T4main5ShapeP 的结构为:{ [24 x i8], metadata, witness_table }
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"protocol witness table for main.Circle : main.Shape in main", i32 0, i32 0), i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.c2 : main.Shape", i32 0, i32 2), align 8
// [24 x i8] 是 24 个 Int8 数组,内存中等价 [3 x i64] 数组,等价于 %T4main5ShapeP = type { [3 x i64], %swift.type*, i8** }
// 这儿是将 %T4main5ShapeP 这个结构体强制转换成 %T4main6CircleC,此刻的结构为:{ [3 x i64], metadata, witness_table }
// 然后把 %5 寄存到 %T4main5ShapeP 的第一个元素。所以最终的结构为:{ [%T4main6CircleC*, i64, i64], metadata, witness_table },
store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"main.c2 : main.Shape" to %T4main6CircleC**), align 8
ret i32 0
}
经过这一段代码的解读也从而验证了第 1 点推断出来的 c2 的内存结构。接下来咱们还需求知道 witness_table 的内存结构,在 IR 中 witness_table 的结构如下:
能够看到,这个 witness_table 的结构中有两个成员,那么根据这个信息,复原出来的 witness_table 的结构如下:
struct TargetWitnessTable{
var protocol_conformance_descriptor: UnsafeRawPointer
var protocol_witness: UnsafeRawPointer
}
那么此刻,ProtoclInstaceStruct 的结构就变成如下代码:
struct ProtoclInstaceStruct {
var heapObj: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeMutablePointer<TargetWitnessTable>
}
3. 源码剖析 witness_table 的内存结构
接下来咱们经过源码来剖析 witness_table 的内存结构,咱们大局查找 TargetWitnessTable,在 Metadata.h 文件中找到 TargetWitnessTable,如图:
留意看,源码中的注释也清楚的写着这个是一个协议的见证表,并且,此刻咱们知道第 2 点剖析出来的 protocol_conformance_descriptor 是一个 TargetProtocolConformanceDescriptor,找到这个结构的界说,发现它有以下成员,如图:
咱们看 Protocol 这个成员变量,它是一个相对类型指针,其类型的结构为 TargetProtocolDescriptor,相对类型指针在《元类型以及 Mirror 源码和 HandyJson 剖析复原枚举、结构体、类的 Metadata》这篇文章中有介绍,并且咱们现已把这个相对类型指针给复原了出来,咱们用的时分直接仿制过来就好了。
现在需求复原 TargetProtocolDescriptor 的结构,TargetProtocolDescriptor 是承继自 TargetContextDescriptor 的,TargetContextDescriptor 咱们应该无比的熟悉了,在上面说到的文章中也有介绍。所以,TargetProtocolDescriptor 必定有 Flags 和 Parent 两个成员变量,咱们再看一下它本身有什么,如图:
此刻此刻,TargetProtocolDescriptor 的结构能够复原出来了,代码如下:
struct TargetProtocolDescriptor {
var Flags: UInt32
var Parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var Name: TargetRelativeDirectPointer<CChar>
var NumRequirementsInSignature: UInt32
var NumRequirements: UInt32
var AssociatedTypeNames: TargetRelativeDirectPointer<CChar>
}
TargetProtocolDescriptor 的结构复原出来后,咱们接着也把 TargetProtocolConformanceDescriptor 的结构复原出来,代码如下:
struct TargetProtocolConformanceDescriptor {
var `Protocol`: TargetRelativeDirectPointer<TargetProtocolDescriptor>
var TypeRef: UnsafeRawPointer
var WitnessTablePattern: UnsafeRawPointer
var Flags: UInt32
}
4. 验证复原出来的 witness_table 的内存结构
经过上面几点呢,咱们把 witness_table 的内存结构复原出来了,复原出来后咱们做一个验证,看看复原的是否正确。
复原出来的完好代码如下:
struct ProtoclInstaceStruct {
var heapObj: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeMutablePointer<TargetWitnessTable>
}
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeMutablePointer<TargetProtocolConformanceDescriptor>
var protocol_witness: UnsafeRawPointer
}
struct TargetProtocolConformanceDescriptor {
var `Protocol`: TargetRelativeDirectPointer<TargetProtocolDescriptor>
var TypeRef: UnsafeRawPointer
var WitnessTablePattern: UnsafeRawPointer
var Flags: UInt32
}
struct TargetProtocolDescriptor {
var Flags: UInt32
var Parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var Name: TargetRelativeDirectPointer<CChar>
var NumRequirementsInSignature: UInt32
var NumRequirements: UInt32
var AssociatedTypeNames: TargetRelativeDirectPointer<CChar>
}
struct TargetRelativeDirectPointer<Pointee> {
var RelativeOffset: Int32
mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee>{
let offset = self.RelativeOffset
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
}
}
}
下面是我的验证代码:
var c2: Shape = Circle(20)
withUnsafePointer(to: &c2) { c2_ptr in
c2_ptr.withMemoryRebound(to: ProtoclInstaceStruct.self, capacity: 1) { pis_ptr in
print(pis_ptr.pointee)
let protocolDesPtr = pis_ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.Protocol.getmeasureRelativeOffset()
print("协议称号:\(String(cString: protocolDesPtr.pointee.Name.getmeasureRelativeOffset()))")
print("协议办法的数量:\(protocolDesPtr.pointee.NumRequirements)")
print("witnessMethod:\(pis_ptr.pointee.witness_table.pointee.protocol_witness)")
}
}
打印结果:
ProtoclInstaceStruct(heapObj: 0x000000010732a1c0, unkown1: 0x0000000000000000, unkown2: 0x0000000000000000, metadata: 0x00000001000081f0, witness_table: 0x0000000100004088)
协议称号:Shape
协议办法的数量:1
witnessMethod:0x00000001000021d0
咱们在剖析 IR 代码的时分,应该有留意到 TargetWitnessTable 的 protocol_witness,这一个其实存储的便是咱们的 witnessMethod,在上面的 IR 代码中其实现已写的很清楚了,但咱们仍是来验证一下。
- 在终端使用
nm -p <可执行文件> | grep <内存地址>
打印出这个办法的符号信息。 - 接着用
xcrun swift-demangle <符号信息>
复原这个符号信息。
如图:
所以,这个协议见证表(witness_table)的实质其实便是 TargetWitnessTable。第一个元素存储的是一个 descriptor,记载协议的一些描绘信息,例如称号和办法的个数等。那么从第二个元素的指针开端存储的便是函数的指针。
留意!ProtoclInstaceStruct 中的 witness_table 变量是一个接连的内存空间,所以这个 witness_table 变量寄存的可能是很多个协议的见证表。
寄存多个协议见证表的因素取决于变量的静态类型,假如这个变量的类型是协议组合类型,那么 witness_table 寄存的便是协议组合中所有协议的见证表,假如这个变量的类型是指定独自的某个协议,那么 witness_table 寄存的只要这个协议的见证表。
四、Existential Container
咱们在第三大点中研讨的对象一直是协议的见证表(witness_table),那么在这个探索的进程,咱们从前复原出 c2 实例的内存布局,也便是 ProtoclInstaceStruct 这个结构。这个是什么呢,咱们来介绍一个东西 – Existential Container。
Existential Container: 它是编译器生成的一种特殊的数据类型,用于办理恪守了相同协议的协议类型,由于这些类型的内存巨细不一致,所以经过当时的 Existential Container 统一办理。
-
关于小容量的数据,直接存储在 Value Buffer。
-
关于大容量的数据,经过堆区分配,存储堆空间的地址。
想说理解的一点便是复原出来的 ProtoclInstaceStruct 其实便是 Existential Container,翻译过来叫做存在容器。这个存在容器最终的两个 8 字节存储的内容是固定的,存储的是这个实例类型的元类型和协议的见证表。
那前面的 24 个字节用来寄存什么:
-
假如这个实例是引证类型,那么第一个 8 字节存储的便是实例在堆空间的地址值。
-
假如这个实例是值类型,当着 24 个字节能够完全存储值类型的内存(也便是值类型的特点值),那么它就直接存储在这 24 个字节里。假如超出了 24 个字节,会经过堆区分配,然后第一个 8 字节存储堆空间的地址。
所以 ProtoclInstaceStruct 的结构应该是这样的:
struct ExistentialContainer {
var valueBuffer1: UnsafeRawPointer
var valueBuffer2: UnsafeRawPointer
var valueBuffer3: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeRawPointer
}
接下来咱们经过一个结构体来验证,为了便利测验,咱们仍是那之前的 Circle 略微改一下,代码如下:
struct Circle: Shape {
var radius = 10
var width: Int
var height: Int
init(_ radius: Int) {
self.radius = radius
self.width = radius * 2
self.height = radius * 2
}
var area: Double {
get {
return Double(radius * radius) * 3.14
}
}
}
var c2: Shape = Circle(10)
print("end")
咱们来看一下它的内存布局,如图:
此刻存在容器的前 24 个字节分别存储着 Circle 的 radius、width 和 height。接下来我添加一个特点 height1,代码如下:
struct Circle: Shape {
var radius: Int
var width: Int
var height: Int
var height1: Int
init(_ radius: Int) {
self.radius = radius
self.width = radius * 2
self.height = radius * 2
self.height1 = radius * 2
}
var area: Double {
get {
return Double(radius * radius) * 3.14
}
}
var area1: Double {
get {
return Double(radius * radius) * 3.14
}
}
}
咱们来看一下它的内存布局,如图:
如图所示,这就验证了前面临存在容器的概念和含义。