协议基本语法
定义申明
如若想使用协议,那么我们需要使用protocol关键字来申明协议。
protocol LProtocol {}
基本语法
-
协议中定义属性
1、协议中要求我们 在定义属性时,我们必须指定属性至少是
可读的,即我们需要给属性添加{ get }属性; 同时我们要注意 这个get并不一定指定属性就是计算属性。2、协议中定义的属性我们要使用
var来修饰例如
- 协议中定义方法
我们在协议中定义方法,只需要写好其定义(方法名称、参数、返回值)就好,不用实现。
- 协议中定义异变方法(枚举、结构体)
异变方法能改变其所属的实例,以及该实例的所有属性。
在类方法中可以不添加 mutating 关键字。
- 协议中定义初始化方法
协议中定义的初始化方法,在遵循这个协议时,我们需要在实现这个初始化方法时 在init前加上required关键字,否则编译器会报错的(类的初始化 器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器)。
或者将类定义成 finil 类型.
- 类专用协议
通过添加 AnyObject 关键字到协议的继承列表,你就可以限制协议只能被类 类型采纳
- 可选协议
1.像OC一样的
optional
这个就是讲若我们的协议不强求遵循者去实现,就可以用 optional 作为前缀放在协议的定义,但需要注意的是我们还需要增加 @objc 关键字
2.用extension实现可选协议
extension LProtocol {
func test(){}
}
协议基本原理
方法调度
在前面# Swift类和结构体(二) 中,我们知道函数是 由 函数表派发 (V-Table) 来实现的,那么在协议中呢?
我们先说结论:其实是PWT (Protocol witness table) 来实现的.
例如
protocol LProtocol {
var name: String { get set }
func test()
}
class LTeacher: LProtocol {
var name: String
var age: Int = 10
init(_ name: String) {
self.name = name
}
func test() {
print("名称:(self.name)")
}
}
var t: LTeacher = LTeacher.init("Json")
t.test()
复制代码
我们通过 SIL 来看下有些啥?
当我们的实例 t 静态类型是 LTeacher 时,我们看到 SIL 中,是通过V-Table 来调用函数的。
通过 LLDB 查看汇编来验证是的,当t 静态类型是 LTeacher 时,的确是通过 V-Table 来调用的
当我们把 t 静态类型 改为 协议 LProtocol 时会发现在SIL 中,
函数调用变成了
witness_method。
通过 LLDB 查看汇编来验证是的,当t 静态类型是 LProtocol 时,的确是通过 PWT (Protocol witness table) 来桥接调用的
总结:
固通过上面的 SIL 和 汇编代码 知道,我们一个实例变量
- 若其静态类型是类 类型的化,则走
V-Table函数表派发调度 - 若其静态类型是协议 类型的化,则走
PWT (Protocol witness table)来桥接调度 到类的V-Table函数表派发调度
注: 同一个类遵循多个协议则就有多个PWT 表。但是类的继承 不会增加 PWT 表个数。
协议extension 调度
当我们在协议定义里没有定义方法时,但我们在 extension 中增加了方法实现,此时方法的调度又是咋样的呢?
protocol LProtocol {
}
extension LProtocol {
func test() {
print("LProtocol")
}
}
class LTeacher: LProtocol {
func test() {
print("LTeacher")
}
}
class LPerson: LProtocol {
}
func opration() {
let t: LTeacher = LTeacher()
t.test()
let t2: LProtocol = LTeacher()
t2.test()
let p: LPerson = LPerson()
p.test()
let p2: LProtocol = LPerson()
p2.test()
}
opration()
复制代码
其打印结果:
我们首先看到 SIL 文件中 PWT (Protocol witness table) 表并没有任何的方法表。
而在 opration() 函数中 看到
固
- 在 extension 中,若定义中没声明,那么除了 类中实现了的是 V-table 派发之外,都是 函数静态派发了。
- 特例是 协议中申明,extension 中 实现,类中未实现,则 是
PWT (Protocol witness table)+ 函数静态派发
总结:
所以有如下表格结论(extension 中实现)
| 类型 | 类中实现 | 类中未实现 | 协议中申明 | 协议中未申明 |
|---|---|---|---|---|
| 类 | ✔️ | V-Table | V-Table | |
| 类 | ✔️ | 静态派发 | 静态派发 | |
| 协议 | ✔️ | PWT+V-Table | 静态派发 | |
| 协议 | ✔️ | PWT+静态派发 | 静态派发 |
协议类型大小测量
对于遵循协议的类,他的大小我们来测量下
protocol Shape {
var radious: Double {get set}
}
class Circle: Shape {
var radious: Double
init(_ radious: Double) {
self.radious = radious
}
}
let circle: Circle = Circle.init(11)
let circle2: Shape = Circle.init(11)
var t = type(of: circle2)
//实例对象大小
print(class_getInstanceSize(Circle.self))
print(class_getInstanceSize(t as! AnyClass))
print(MemoryLayout<Circle>.size)
print(MemoryLayout<Shape>.size)
print(MemoryLayout.size(ofValue: circle))
print(MemoryLayout.size(ofValue: circle2))
打印结果
24
24
8
40
8
40
Program ended with exit code: 0
复制代码
这里 可以看到 通过 MemoryLayout 测量的数据,在 确定类型变量和协议变量的大小是不同的,这也就证明了两个实例在底层的数据结构是不同的。
继承协议类型内存分析
通过LLDB 查看两个不同大小的内存信息
所以通过内存的分析大致可以得出这样的结构体
struct LProtoclStruct {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeRawPointer
}
复制代码
我们继续用IR 来查看到 这个语句
@"$s4main6CircleCAA5ShapeAAWP" = hidden constant [4 x i8*] [i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main6CircleCAA5ShapeAAMc" to i8*), i8* bitcast (double (%T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP7radiousSdvgTW" to i8*), i8* bitcast (void (double, %T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP7radiousSdvsTW" to i8*), i8* bitcast ({ i8*, %TSd* } (i8*, %T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP7radiousSdvMTW" to i8*)], align 8
复制代码
通过这个 我们看到其实我们是对 witness_table 中添加了 一个 descriptor 和 几个方法列表。 所以我们得出
struct TargetWitnessTable{
var protocolConformanceDescriptor: UnsafeRawPointer
var protocolWitness: UnsafeRawPointer
}
复制代码
源码分析协议 witness_table
我们在 Swift-5.5.2 中直接搜索,protocolConformanceDescriptor 可以看到
在 TargetProtocolConformanceDescriptor 结构体中 有这样的定义
所以的到这个结构体
struct TargetProtocolConformanceDescriptor {
var ProtocolDec: TargetRelativeDirectPointer<TargetProtocolDescriptor>
var TypeRef: UnsafeRawPointer
var WitnessTablePattern: UnsafeRawPointer
var Flags: UInt32
}
复制代码
在 TargetProtocolDescriptor 中是
而 TargetProtocolDescriptor 是继承自TargetContextDescriptor。
所以我们能的到
struct TargetProtocolDescriptor {
var Flags: UInt32
var Parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var Name: TargetRelativeDirectPointer<CChar>
var NumRequirementsInSignature: UInt32
var NumRequirements: UInt32
var AssociatedTypeNames: TargetRelativeDirectPointer<CChar>
}
复制代码
其中 TargetRelativeDirectPointer 我们在Swift源码还原StructMetadata 中有定义。
整体代码
struct LProtoclStruct {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeMutablePointer<TargetWitnessTable>
}
struct TargetWitnessTable{
var protocolConformanceDescriptor: UnsafeMutablePointer<TargetProtocolConformanceDescriptor>
var protocolWitness: UnsafeRawPointer
}
struct TargetProtocolConformanceDescriptor {
var ProtocolDec: 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 offset: Int32
mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee>{
let offset = self.offset
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
}
}
}
withUnsafePointer(to: &circle2) { ptr in
ptr.withMemoryRebound(to: LProtoclStruct.self, capacity: 1) { pPtr in
print(pPtr.pointee)
let protocolDesPtr = pPtr.pointee.witness_table.pointee.protocolConformanceDescriptor.pointee.ProtocolDec.getmeasureRelativeOffset()
print("Protocol Name:(String(cString: protocolDesPtr.pointee.Name.getmeasureRelativeOffset()))")
print("Protocol Method Number:(protocolDesPtr.pointee.NumRequirements)")
print("witnessMethod address:(pPtr.pointee.witness_table.pointee.protocolWitness)")
}
}
复制代码
通过打印的值,已经验证了我们的 witness_table 并和前面的 IR 结果相对应。
总结:
- 每个遵守了协议的类,都会有自己的PWT,遵守的协议越多,PWT中存储的函数地址就越多
- PWT的本质是一个指针数组,第一个元素存储
TargetProtocolConformanceDescriptor,其后面存储的是连续的函数地址 - PWT的数量与协议数量一致
Existential Container-存在容器
Existential container 是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协议类型,因为这些类型的内存大小不一致,所以通过当前的 Existential Container 统一做管理
它遵循两个原则
- 对于小容量的数据,直接存储在 Value Buffer (小于等于24字节)
- 对于大容量的数据,通过堆区分配,存储堆空间的地址
这个 存在容器 其实就是操作的 LProtoclStruct 信息,对前 3 个8字节做信息存储
- 若对象是引用类型实例,则前8 字节是实例地址的信息
- 若对象是值类型实例,则前24 字节是属性值信息,或者前8 字节是 存放属性值的地址空间地址信息
例如
protocol Shape {
var radious: Double {get set}
}
struct Circle: Shape {
var radious: Double
var radious2: Double = 12
// var radious3: Double = 13
// var radious4: Double = 14
// var radious5: Double = 15
// var radious6: Double = 16
init(_ radious: Double) {
self.radious = radious
}
}
var circle: Circle = Circle.init(11)
var circle2: Shape = Circle.init(11)
var t = type(of: circle2)
//实例对象大小
//print(class_getInstanceSize(Circle.self))
//print(class_getInstanceSize(t as! AnyClass))
print(MemoryLayout<Circle>.size)
print(MemoryLayout<Shape>.size)
print(MemoryLayout.size(ofValue: circle))
print(MemoryLayout.size(ofValue: circle2))
复制代码
引用类型实例
值类型实例
当 2个 变量时 是
当 3个 变量时 是
当 4个 变量时 是
可以看到确实是 小容量时 是直接存储在 Value Buffer 中,大容量的 则放在了一个新 分配的堆地址空间中
写时复制
看在 值类型实例 时,我们是分配的堆空间地址,但是在值类型中,需要复合值类型的特性,所以就来了个 写时复制, 以达到这个要求。
在赋值时是一样的地址空间,但是改变了一个属性变量时,这个相同的地址空间就变化了,固达到要求,还减少了不要的消耗。