swift-协议
一 协议与继承
class LGTeacher{ var age = 10 var name = "swift" } class LGStudent{ var name = "oc" var score = 100 }
对于上面的两个类来说,如果需要为这两个类都添加一个debug函数,直观的方法是为每个都添加一个debug函数,但是当我们的代码中存在很多个这样的类来说显然是不合理的。从继承的角度来说,我们可以构建一个公共类,使得当前的类都继承自公共类,子类实现函数重载来达到目的,但是这种方式对于我们的代码来说具有较强的入侵性,那么我们还有什么办法呢,我们就可以通过一个协议来描述当前类的共同行为,,并使用 extension 方法来对我们的类进行扩展,这样就很棒。
extension LGTeacher:CustomStringConvertible{ var description: String{get{return "LGTeacher"}} } extension LGStudent:CustomStringConvertible{ var description: String{get{return "LGStudent"}} } func print(subject:CustomStringConvertible){ let string = data.description //to do sth }
我们可以做一下总结:
- class本质上定义了一个对象是什么,有什么属性以及行为
- protocol本质上是约束类的共同行为
二、协议语法
接下来,我们来学习一下协议。
- 协议的属性必须声明是get/get和set
- 协议用 protocol 关键字
- 协议内只负责方法的声明(方法名、属性、返回值),不做具体实现
- 遵循协议的类需要实现相应的方法
- 声明get的不一定是计算属性
protocol MyProtocol{ var age : Int{get set} var name : String{get} } class LGTeacher{ var age :Int = 18 var name :String init(name:String){ self.name = name } }
- 协议中的异变方法 mutating 表示该方法可以改变其所属的实例以及该实例的所有属性(用于枚举和结构体),在类中实现该方法时不需要加mutating关键字,结构体需要加
protocol Togglable{ mutating func toggle() } class A:Togglable{ func toggle() { } } struct B:Togglable{ mutating func toggle() { } }
- 类在实现协议的初始化器中必须使用required关键字来修饰初始化器的实现(表示该类的所有子类都必须实现该初始化器),如果该类不需要被继承则不需要加关键字
protocol Incrementable{ init(_name:String) } class A :Incrementable{ var name : String = "" required init(_name: String) { self.name = _name } }
- 类专用协议可通过添加AnyObject关键字到协议的继承列表中,这样就可以限制该协议只可被类使用
protocol Incrementable:AnyObject{ init(_name:String) } class A :Incrementable{ var name : String = "" required init(_name: String) { self.name = _name } }
- 可选协议:如果噩梦不想强制遵循协议的类实现,可使用optional作为前缀放在协议中
@objc protocol Incrementable{
@objc optional func increment(by:Int)
}
三、协议原理探究
3.1 协议表(PWT-witness table)
protocol Incrementable{ func incrementable(by:Int) } class A:Incrementable{ var name:String = "" func incrementable(by: Int) { print("swift") } } var a = A() a.incrementable(by: 10) print("end")
- class_method通过V-Table调度
- witness_method通过witness_table调度
3.2 协议原理
protocol Incrementable{ var radious:Double{get set} } class A:Incrementable{ var radious: Double = 20.0 } var a :Incrementable = A() print("end")
通过分析IR代码,还原出结构体protocolBox
struct ProtocolBox { var valueBuffer1: UnsafeRawPointer var valueBuffer2: UnsafeRawPointer var valueBuffer3: UnsafeRawPointer var metadata: UnsafeRawPointer var witness_table: UnsafeMutablePointer<TargetWitnessTable> }
我们尝试打印下面这个结构体:
protocol Incrementable{ var radious:Double{get set} } struct A:Incrementable{ var radious:Double = 20.0 var width:Double = 20 } class B:Incrementable{ var radious: Double = 20.0 } var a : Incrementable = A() var b : Incrementable = B() print("end")
最终,我们通过源码分析TargetWitnessTable、TargetProtocolConformanceDescriptor、TaegetProtocolDescriptor得到最终结构:
自此,我们还原出协议的结构
struct OSProtocolBox { var heapObject: UnsafeRawPointer var unkonw1: UnsafeRawPointer var unkonw2: UnsafeRawPointer var metadata: UnsafeRawPointer var witnessTable: UnsafeMutablePointer<TargetWitnessTable> } struct TargetWitnessTable{ var protocol_conformance_descriptor: UnsafeMutablePointer<TargetProtocolConformanceDescriptor> var witnessMethod: UnsafeRawPointer } struct TargetProtocolConformanceDescriptor{ var protocolDesc: 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> }
总结:
- 每个遵守了协议的类,都会有自己的PWT,遵守的协议越多,PWT中存储的函数地址就越多
- PWT的本质是一个指针数组,第一个元素存储TargetProtocolConformanceDescriptor,其后面存储的是函数地址PWT的数量与协议数量一致
- Existential Container 是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协 议类型,因为这些类型的内存大小不一致,所以通过当前的 Existential Container 统一管理
- 对于小容量的数据,直接存储在 Value Buffe
- r对于大容量的数据,通过堆区分配,存储堆空间的地址