Swift -协议protocol

708 阅读7分钟

协议基本语法

定义申明

如若想使用协议,那么我们需要使用protocol关键字来申明协议。

protocol LProtocol {}

基本语法

  • 协议中定义属性

    1、协议中要求我们 在定义属性时,我们必须指定属性至少是可读的,即我们需要给属性添加 { get } 属性; 同时我们要注意 这个 get 并不一定指定属性就是计算属性。

    2、协议中定义的属性我们要使用 var 来修饰

    例如

image.png

  • 协议中定义方法

我们在协议中定义方法,只需要写好其定义(方法名称、参数、返回值)就好,不用实现。

image.png

  • 协议中定义异变方法(枚举、结构体)

异变方法能改变其所属的实例,以及该实例的所有属性。

在类方法中可以不添加 mutating 关键字。

image.png

  • 协议中定义初始化方法

image.png

协议中定义的初始化方法,在遵循这个协议时,我们需要在实现这个初始化方法时 在init前加上required关键字,否则编译器会报错的(类的初始化 器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器)。

image.png

或者将类定义成 finil 类型.

image.png

  • 类专用协议

通过添加 AnyObject 关键字到协议的继承列表,你就可以限制协议只能被类 类型采纳

image.png

  • 可选协议 1.像OC一样的optional

这个就是讲若我们的协议不强求遵循者去实现,就可以用 optional 作为前缀放在协议的定义,但需要注意的是我们还需要增加 @objc 关键字

image.png

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 来调用函数的。

image.png

image.png

通过 LLDB 查看汇编来验证是的,当t 静态类型是 LTeacher 时,的确是通过 V-Table 来调用的

image.png image.png

当我们把 t 静态类型 改为 协议 LProtocol 时会发现在SIL 中,

image.png 函数调用变成了witness_method

image.png

通过 LLDB 查看汇编来验证是的,当t 静态类型是 LProtocol 时,的确是通过 PWT (Protocol witness table) 来桥接调用的

image.png

image.png

总结:

固通过上面的 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()
复制代码

其打印结果:

image.png

我们首先看到 SIL 文件中 PWT (Protocol witness table) 表并没有任何的方法表。

image.png

而在 opration() 函数中 看到

image.png

  • 在 extension 中,若定义中没声明,那么除了 类中实现了的是 V-table 派发之外,都是 函数静态派发了。
  • 特例是 协议中申明,extension 中 实现,类中未实现,则 是 PWT (Protocol witness table) + 函数静态派发

总结:

所以有如下表格结论(extension 中实现)

类型类中实现类中未实现协议中申明协议中未申明
✔️V-TableV-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 查看两个不同大小的内存信息

image.png

image.png

所以通过内存的分析大致可以得出这样的结构体

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
复制代码

image.png

通过这个 我们看到其实我们是对 witness_table 中添加了 一个 descriptor 和 几个方法列表。 所以我们得出

struct TargetWitnessTable{

    var  protocolConformanceDescriptor: UnsafeRawPointer
    var  protocolWitness: UnsafeRawPointer
}
复制代码

源码分析协议 witness_table

我们在 Swift-5.5.2 中直接搜索,protocolConformanceDescriptor 可以看到

image.png

在 TargetProtocolConformanceDescriptor 结构体中 有这样的定义

image.png

所以的到这个结构体

struct TargetProtocolConformanceDescriptor {

    var ProtocolDec: TargetRelativeDirectPointer<TargetProtocolDescriptor>
    var TypeRef: UnsafeRawPointer
    var WitnessTablePattern: UnsafeRawPointer
    var Flags: UInt32
}

复制代码

在 TargetProtocolDescriptor 中是

image.png

而 TargetProtocolDescriptor 是继承自TargetContextDescriptor

image.png

所以我们能的到

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)")
    }
}

复制代码

image.png

通过打印的值,已经验证了我们的 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))
复制代码

引用类型实例

image.png

值类型实例

当 2个 变量时 是

image.png

当 3个 变量时 是

image.png

当 4个 变量时 是

image.png

可以看到确实是 小容量时 是直接存储在 Value Buffer 中,大容量的 则放在了一个新 分配的堆地址空间中

写时复制

看在 值类型实例 时,我们是分配的堆空间地址,但是在值类型中,需要复合值类型的特性,所以就来了个 写时复制, 以达到这个要求。

image.png

image.png

在赋值时是一样的地址空间,但是改变了一个属性变量时,这个相同的地址空间就变化了,固达到要求,还减少了不要的消耗。