一.协议与继承
class LGTeacher{
var age = 10
var name = "Kody"
}
class Dog{
var name = "糯米"
var type = "白梗"
}
/*
这里有2个类,LGTeacher与Dog,此时想为2个类添加debug函数去打印类相关的信息
从继承的角度上看,我们会封装一个Animal类(公共的基类)。从业务逻辑来说,这么处理不太合适
*/
那么最直观也是最简单的办法就是,给每一个类添加一个debug函数
class LGTeacher{
var age = 10
var name = "Kody"
func debug() {
print(...)
}
}
class Dog{
var name = "糯米"
var type = "白梗"
func debug() {
print(...)
}
}
如果我们对当前代码中的每个类都需要添加debug函数
,显然上面这种方法是行不通的,于是有了下面的代码
func debug(subject: Any){
print(.....)
}
当然看到这里可能会觉得没有问题,如果我们想要描述当前类的具体信息,这个时候我们还需要引入一个公共的基类,同时我们还需要有一个公共的属性description
来让子类重载,这无疑是对我们的代码是很强的入侵
。
所以这个时候我们通过协议来描述当前类的具体行为,并通过extension
的方式来对我们的类进行扩展,这无疑是最好的办法
extension LGTeacher: CustomStringConvertible {
var description: String {
get {
return "LGTeacher: \(age)\(name)"
}
}
}
extension Dog: CustomStringConvertible {
var description: String {
get {
return "Dog: \(name)\(type)"
}
}
}
func debug(subject: CustomStringConvertible){
print(subject.description)
}
let t = LGTeacher()
let d = Dog()
debug(subject: t)
debug(subject: d)
看到这里我们就可以稍微的总结一下
-
Class
本质上定义了一个对象是什么 -
Protocol
本质上定义了一个对象有哪些行为
二.协议的基本语法
1.协议要求一个属性必须明确是get
或get和set
protocol MyProtocol {
//必须是var声明的
var age: Int { get set}
//要求遵循协议的类/结构体必须要实现get方法
var name: String { get }
}
//需要注意的是:并不是当前声明get的属性一定是计算属性
class LGTeacher: MyProtocol {
var age: Int
//此时的name并不是计算属性
var name: String
init(_ age: Int,_ name: String) {
self.age = age
self.name = name
}
}
2.协议中的异变方法,表示在该方法可以改变其属性的实例,以及该实例的所有属性(用于枚举和结构体),在为类实现该方法的时候不需要写mutating
关键字
protocol MyProtocol {
mutating func test()
}
3.类在实现协议中的初始化器,必须使用required
关键字修饰初始化器的实现(类的初始化器添加required
修饰符来表明所有该类的子类如果要自定义初始化器
就必须实现该初始化器)
关于required
在类与结构体中初始化器模块有详细讲解
protocol MyProtocol {
init()
}
class LGPerson: MyProtocol {
required init() {}
}
//添加final关键字后,就不需要required。因为该类不允许被继承,也就没有了子类实现该初始化器的说法了
final class LGStudent: MyProtocol {
init() {}
}
4.类专用协议(通过添加AnyObject
关键字到协议的继承列表,就可以限制只能被类类型采纳)
在Mirror源码解析也讲解到了AnyObject
protocol MyProtocol: AnyObject {}
5.可选协议:不想强制让遵循协议的类类型实现
//定义一个可选协议一般有两种方式
/*
方式1:使用@objc关键字,使用OC方式来使用optional声明去可选协议
1.暴露给Objc运行时,依旧是函数表派发(如果是@objc + dynamic会改变为消息派发方式(objc_msgSend))
2.值类型不能使用该protocol,只能被class使用
*/
@objc protocol MyProtocol {
@objc optional func test()
}
class LGTeacher: MyProtocol {
// func test() {
// print("test")
// }
}
/*
方式2:使用extension来给出默认实现,来实现可选协议的功能
一般我们在Swift中会使用这种方式来实现可选协议
*/
protocol OptionalProtocol{
func method() //必须实现
func method1() //可选
func method2() //可选
}
extension OptionalProtocol {
func method1() {}
func method2() {}
}
这里总结一下@objc的使用
1.Selector
中调用的方法需要在方法前声明@objc
,目的是允许这个函数在运行时通过 Objective-C 的消息机制调用
let btn = UIButton()
btn.addTarget(self, action: #selector(click), for: .touchUpInside)
@objc func click() {
print("clicked")
}
2.协议的方法可选时,协议和方法前面都要加上@objc
@objc protocol MyProtocol {
@objc optional func test()
}
3.用weak
修饰协议时,协议前面要添加@objc
@objc protocol MyProtocol {
}
class LGTeacher {
weak var delegate: MyProtocol?
}
4.类前面加上@objcMembers
,那么它及其子类、扩展的方法都会隐式的加上@objc
@objcMembers
class LGTeacher {
}
如果此时不想在扩展里加@objc
,可以使用@nonobjc
修饰
@objcMembers
class LGTeacher {
}
@nonobjc extension LGTeacher {
func test() {}
}
5.扩展前加上@objc
,那么里面的方法都会隐式加上@objc
class LGTeacher {
}
@objc extension LGTeacher {
func test() {}
}
6.函数前面加上@objc
class LGTeacher {
//加上@objc将该函数暴露给Runtime,依旧是函数表派发
@objc func test() {}
}
//加上@objc就可以使用Runtime相关API
let sel = #selector(LGTeacher.test)
let t = LGTeacher()
//当然这里只有让LGTeacher继承自NSObject才能执行这个sel
在类与结构体(下)中函数派发方式模块中也有对@objc
的讲解
二.协议原理探究
1.实例对象执行协议函数
protocol MyProtocol {
func test()
}
class LGTeacher: MyProtocol {
//v-table
func test() {
print("test")
}
}
let t = LGTeacher()
t.test()
在类与结构体(下)讲解到Swift类
函数派发方式为函数表调度
,那么这里的test()
是函数表的调度吗?这里我们通过SIL来分析一下
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1tAA9LGTeacherCvp // id: %2
%3 = global_addr @$s4main1tAA9LGTeacherCvp : $*LGTeacher // users: %8, %7
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
%5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
store %6 to %3 : $*LGTeacher // id: %7
%8 = load %3 : $*LGTeacher // users: %9, %10
//这里的class_method为函数表调度方式
%9 = class_method %8 : $LGTeacher, #LGTeacher.test : (LGTeacher) -> () -> (), $@convention(method) (@guaranteed LGTeacher) -> () // user: %10
%10 = apply %9(%8) : $@convention(method) (@guaranteed LGTeacher) -> ()
%11 = integer_literal $Builtin.Int32, 0 // user: %12
%12 = struct $Int32 (%11 : $Builtin.Int32) // user: %13
return %12 : $Int32 // id: %13
} // end sil function 'main'
//test函数声明在v-table当中
sil_vtable LGTeacher {
#LGTeacher.test: (LGTeacher) -> () -> () : @$s4main9LGTeacherC4testyyF // LGTeacher.test()
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
sil_witness_table hidden LGTeacher: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}
class_method函数表调度
因此从SIL分析得出,遵循该协议的类实现该协议方法,通过实例对象调用协议方法,还是函数表的调度
2.协议类型实例执行协议函数
此时我们把上面代码中的let t = LGTeacher()
改为let t: MyProtocol = LGTeacher()
//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
SIL代码
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1tAA10MyProtocol_pvp // id: %2
%3 = global_addr @$s4main1tAA10MyProtocol_pvp : $*MyProtocol // users: %9, %7
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
%5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %8
%7 = init_existential_addr %3 : $*MyProtocol, $LGTeacher // user: %8
store %6 to %7 : $*LGTeacher // id: %8
%9 = open_existential_addr immutable_access %3 : $*MyProtocol to $*@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol // users: %11, %11, %10
//此时的调度方式变成了witness_method
%10 = witness_method $@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11
%11 = apply %10<@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol>(%9) : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9
%12 = integer_literal $Builtin.Int32, 0 // user: %13
%13 = struct $Int32 (%12 : $Builtin.Int32) // user: %14
return %13 : $Int32 // id: %14
} // end sil function 'main'
// test函数依旧声明在v-table当中
sil_vtable LGTeacher {
#LGTeacher.test: (LGTeacher) -> () -> () : @$s4main9LGTeacherC4testyyF // LGTeacher.test()
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
//此时多了一个witness_table。为每一个遵循协议的类记录实现协议相关的编码信息,也就是保存了协议函数的实现地址
sil_witness_table hidden LGTeacher: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}
witness_method会去查找这个类的witness-table(协议见证表)
中找到方法的实现
关于此时witness_table
中的s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW
,也就是test()
// protocol witness for MyProtocol.test() in conformance LGTeacher
sil private [transparent] [thunk] @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed LGTeacher) -> () {
// %0 // user: %1
bb0(%0 : $*LGTeacher):
%1 = load %0 : $*LGTeacher // users: %2, %3
%2 = class_method %1 : $LGTeacher, #LGTeacher.test : (LGTeacher) -> () -> (), $@convention(method) (@guaranteed LGTeacher) -> () // user: %3
%3 = apply %2(%1) : $@convention(method) (@guaranteed LGTeacher) -> ()
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function '$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW'
通过witness_table
中的函数其实还是通过函数表调度方式调度LGTeacher.test
简单总结一下:witness_table
做了一层桥接,通过witness_table
为每个实现协议的类记录实现协议的编码信息,找到具体的函数实现,完成方法的调度。简单的理解,协议见证表记录录的就是实现的协议函数具体信息
比如说,当LGTeacher
遵循了协议MyProtocol
并实现了协议函数test()
,那么通过协议类型
调用函数test()
时,就会创建一个witness_table
,通过witness_table
找到具体的函数实现,完成函数的调度。简单的来说就是利用witness_table
做了一次桥接。
3.Arm64汇编分析witness_table
通过Arm64汇编代码
分析,在t.test()
打上一个断点
通过我们对汇编基础的认知,很明显t.test()
对于的汇编代码为0x104f17a7c <+128>: blr x8
。读取的内存x1加上0x8地址,存入x8
在blr x8
行打上断点,读取寄存器x8的值
//发现x8就是LGTeacher协议见证表中对test函数的
(lldb) register read x8
x8 = 0x0000000104f17d04 projectTest`protocol witness for projectTest.MyProtocol.test() -> () in conformance projectTest.LGTeacher : projectTest.MyProtocol in projectTest at <compiler-generated>
进入blr x8
此时,这里的blr x8
才是真正的test函数
实现
断点打在blr x8
,并读取寄存器x8
(lldb) register read x8
x8 = 0x0000000104f17ab4 projectTest`projectTest.LGTeacher.test() -> () at main.swift:26
至此,通过汇编还原了witness_table
的原理,也证实了通过SIL分析的逻辑。
问题1:两个类继承自同一协议会有一张还是两张witness_table?
答案肯定是两张,因为从witness_table为每个遵循协议的类记录实现协议函数的相关信息。
问题2:以下代码打印的值为什么?
1.extension添加默认实现
protocol MyProtocol {
func test()
}
//为协议添加默认实现
extension MyProtocol {
func test() {
print("MyProtocol")
}
}
class LGTeacher: MyProtocol {
//v-table
func test() {
print("LGTeacher")
}
}
//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()
当然是LGTeacher
,因为添加的默认实现和通过witness_table
找到函数的调用没有任何的关系。执行的逻辑是,通过witness_table找到在LGTeacher
中对test
的函数,完成函数的调用
2.如果注释掉func test()
protocol MyProtocol {
// func test()
}
//为协议添加默认实现
extension MyProtocol {
func test() {
print("MyProtocol")
}
}
class LGTeacher: MyProtocol {
//v-table
func test() {
print("LGTeacher")
}
}
//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()
答案是MyProtocol
,因为对于t来说静态类型为MyProtocol
,当执行到t.test()
时,由于协议中没有了test函数
,因此不会走协议见证表那套逻辑。而在extension MyProtocol
中有test函数
的实现,因此直接静态派发执行test函数
对应的的SIL
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
...
//直接执行MyProtocol.test()
// function_ref MyProtocol.test()
%10 = function_ref @$s4main10MyProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %11
...
} // end sil function 'main'
//此时的witness_table就为空了
sil_witness_table hidden LGTeacher: MyProtocol module main {
}
3.注释掉LGTeacher
中的test函数
实现
protocol MyProtocol {
func test()
}
//为协议添加默认实现
extension MyProtocol {
func test() {
print("MyProtocol")
}
}
class LGTeacher: MyProtocol {
//v-table
// func test() {
// print("LGTeacher")
// }
}
//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()
其实这个也很好理解,此时的witness_table中实现协议的信息存的是extension
中的默认实现,因此肯定打印为MyProtocol
。
对应的SIL
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
...
%10 = witness_method $@opened("30ED99C2-93C7-11EC-A66A-501FC65B9E38") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("30ED99C2-93C7-11EC-A66A-501FC65B9E38") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11
...
} // end sil function 'main'
// protocol witness for MyProtocol.test() in conformance LGTeacher
sil private [transparent] [thunk] @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : LGTeacher> (@in_guaranteed τ_0_0) -> () {
// %0 // user: %2
bb0(%0 : $*τ_0_0):
//这里直接派发执行MyProtocol.test()
// function_ref MyProtocol.test()
%1 = function_ref @$s4main10MyProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %2
%2 = apply %1<τ_0_0>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> ()
%3 = tuple () // user: %4
return %3 : $() // id: %4
} // end sil function '$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW'
//此时vtable里没有了test函数
sil_vtable LGTeacher {
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
//协议见证表里里有test函数 ---> s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW
sil_witness_table hidden LGTeacher: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}
4.探究witness_table存放位置
我们在研究vTable
的时候,得出vTable
是存在TargetClassDescriptor
中的
接下来来探究witness_table
protocol Shape {
var area: Double { get }
}
class Circle: Shape {
var radious: Double
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
//1.静态类型为Circle
var circle: Circle = Circle(10)
//这里很好理解,一个实例对象为16字节(metadata + refCount) + Double(8字节)
print(class_getInstanceSize(Circle.self)) // 24
//这个8也很好理解,说白了就是circle变量指针的大小。占据8字节大小
print(MemoryLayout.size(ofValue: circle)) // 8
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x0000000100010480
0 elements
0x0000000100010480 ---> 变量circle的地址
(lldb) x/8g 0x0000000100010480
0x100010480: 0x0000000100724c40 0x0000000000000000
0x100010490: 0x0000000000000000 0x0000000000000000
0x1000104a0: 0x0000000000000000 0x0000000000000000
0x1000104b0: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000100724c40
0x100724c40: 0x00000001000103c8 0x0000000000000003
0x100724c50: 0x4024000000000000 0x0000000100724e40
0x100724c60: 0x00000009a0080001 0x00007ff843a06f00
0x100724c70: 0x0000000000000000 0x00007ff8422362d0
0x00000001000103c8 ---> metadata
0x0000000000000003 ---> refCount
0x4024000000000000 ---> Double值10
(lldb) cat address 0x0000000100724c40
address:0x0000000100724c40, (String) $R1 = "0x100724c40 heap pointer, (0x20 bytes), zone: 0x7ff84226d000"
浮点数的还原(lldb调试)
(lldb) expr -f float -- 0x4024000000000000
(Int) $R2 = 10
得出结论:var circle: Circle = Circle(10),存的是堆空间的内存地址
*/
//2.静态类型为Shape
var shape: Shape = Circle(10)
//type(of:)获取的实际类型为Circle,因此大小肯定和Circle一样也是24
print(class_getInstanceSize(type(of: shape) as? AnyClass)) // 24
/*
此时的数据类型占据的内存大小为40,和上面的8完全就不一样了。
但是我们可以得出一个结论,静态类型不同,变量存储的内容是不一样的。
猜想:肯定是多了witness_table的数据?
*/
print(MemoryLayout.size(ofValue: shape)) // 40
/*
(lldb) po withUnsafePointer(to: &shape) {print($0)}
0x0000000100010488
0 elements
(lldb) x/8g 0x0000000100010488
0x100010488: 0x000000010b311280 0x0000000000000000
0x100010498: 0x0000000000000000 0x00000001000103c8
0x1000104a8: 0x000000010000c2e8 0x0000000000000000
0x1000104b8: 0x0000000000000000 0x0000000000000000
前5个8字节存放的就是shape的内容(40字节)
分析第一个8字节 ---> 0x000000010b311280(实例对象的堆空间地址)
(lldb) x/8g 0x000000010b311280
0x10b311280: 0x00000001000103c8 0x0000000000000003
0x10b311290: 0x4024000000000000 0x00007ff84223a498
0x10b3112a0: 0x0000000000000001 0x00007ff84223a8e0
0x10b3112b0: 0x0000000000000007 0x00007ff84223a240
第二、三个8字节都为0
分析第四个8字节 ---> 0x00000001000103c8(实例对象的metadata,动态类型的metadata)
分析第五个8字节 ---> 0x000000010000c2e8(witness table)
(lldb) cat address 0x000000010000c2e8
address:0x000000010000c2e8, 1c8protocol witness table for swiftTest.Circle : swiftTest.Shape in swiftTest <+0> , ($s9swiftTest6CircleCAA5ShapeAAWP), External: NO swiftTest.__DATA_CONST.__const +1c8
*/
expr -f float -- 地址
为浮点数的还原(lldb)
根据上述分析,大致得出的shape
数据结构
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeRawPointer
}
分析IR代码得出确定的类型,此时只留var shape: Shape = Circle(10)
这行代码
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
//获取Circle的metadata
%3 = call swiftcc %swift.metadata_response @"$s4main6CircleCMa"(i64 0) #10
//提取%3到%4,%4也是metadata
%4 = extractvalue %swift.metadata_response %3, 0
//$s4main6CircleCyACSdcfC ---> main.Circle.__allocating_init(Swift.Double) -> main.Circle
//创建Circle实例变量
%5 = call swiftcc %T4main6CircleC* @"$s4main6CircleCyACSdcfC"(double 1.000000e+01, %swift.type* swiftself %4)
//$s4main5shapeAA5Shape_pvp ---> main.shape : main.Shape
//%T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
//%swift.type = type { i64 }
//将metadata存入main.shape
store %swift.type* %4, %swift.type** getelementptr inbounds
(%T4main5ShapeP, %T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp", i32 0, i32 1), align 8
//$s4main6CircleCAA5ShapeAAWP ---> protocol witness table for main.Circle : main.Shape in main
//将数组的第一个元素也存进main.shape中第三个元素中
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6CircleCAA5ShapeAAWP", i32 0, i32 0),
i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp", i32 0, i32 2), align 8
//将创建Circle实例变量存入main.shape的第一个元素位置
store %T4main6CircleC* %5, %T4main6CircleC**
bitcast (%T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp" to %T4main6CircleC**), align 8
ret i32 0
}
//s4main6CircleCAA5ShapeAAMc 存入第一个元素中
//s4main6CircleCAA5ShapeA2aDP4areaSdvgTW 存入第二个元素中(遵循协议的方法)
//$s4main6CircleCAA5ShapeAAMc ---> protocol conformance descriptor for main.Circle : main.Shape in main
//$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW ---> protocol witness for main.Shape.area.getter : Swift.Double in conformance main.Circle : main.Shape in main
@"$s4main6CircleCAA5ShapeAAWP" = hidden constant [2 x i8*]
[i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main6CircleCAA5ShapeAAMc" to i8*),
i8* bitcast (double (%T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW" to i8*)], align 8
可以分析出TargetWitnessTable
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeRawPointer
var witnessMethod: UnsafeRawPointer
}
关于protocol_conformance_descriptor
,我们可以去源码Metadata.h
中找到
/// A witness table for a protocol.
///
/// With the exception of the initial protocol conformance descriptor,
/// the layout of a witness table is dependent on the protocol being
/// represented.
template <typename Runtime>
class TargetWitnessTable {
/// The protocol conformance descriptor from which this witness table
/// was generated.
ConstTargetMetadataPointer<Runtime, TargetProtocolConformanceDescriptor>
Description;
public:
const TargetProtocolConformanceDescriptor<Runtime> *getDescription() const {
return Description;
}
};
这里的Description
就是我们要找的
进入TargetProtocolConformanceDescriptor
找到成员
/// The protocol being conformed to.
TargetRelativeContextPointer<Runtime, TargetProtocolDescriptor> Protocol;
// Some description of the type that conforms to the protocol.
TargetTypeReference<Runtime> TypeRef;
/// The witness table pattern, which may also serve as the witness table.
RelativeDirectPointer<const TargetWitnessTable<Runtime>> WitnessTablePattern;
/// Various flags, including the kind of conformance.
ConformanceFlags Flags;
进入TargetProtocolDescriptor
struct TargetProtocolDescriptor final
: TargetContextDescriptor<Runtime>,
swift::ABI::TrailingObjects<
TargetProtocolDescriptor<Runtime>,
TargetGenericRequirementDescriptor<Runtime>,
TargetProtocolRequirement<Runtime>>
{
...
/// The name of the protocol.
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;
/// The number of generic requirements in the requirement signature of the
/// protocol.
uint32_t NumRequirementsInSignature;
/// The number of requirements in the protocol.
/// If any requirements beyond MinimumWitnessTableSizeInWords are present
/// in the witness table template, they will be not be overwritten with
/// defaults.
uint32_t NumRequirements;
/// Associated type names, as a space-separated list in the same order
/// as the requirements.
RelativeDirectPointer<const char, /*Nullable=*/true> AssociatedTypeNames;
...
}
根据源码补充TargetWitnessTable
数据结构,并验证
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafePointer<TargetWitnessTable>
}
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeMutablePointer<ProtocolConformanceDescriptor>
var witnessMethod: UnsafeRawPointer
}
struct ProtocolConformanceDescriptor {
var protocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
var typeRef: UnsafeRawPointer
var witnessTablePattern: UnsafeRawPointer
var flags: UInt32
}
//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>
}
var ptr = withUnsafePointer(to: &shape){UnsafeRawPointer($0).assumingMemoryBound(to: LGProtocolBox.self)}
var descPtr = ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset()
print(String(cString: descPtr.pointee.name.getMeasureRelativeOffset())) // Shape
print(ptr.pointee.witness_table.pointee.witnessMethod) // 0x0000000100004d40
//本质上执行area中get,还是通过Circle找到witness-table(PWT)再找到对应的的函数地址,开始调用
终端还原一下0x0000000100004d40
❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004d40
0000000100004d40 t _$s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW
❯ xcrun swift-demangle s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW
$s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW ---> protocol witness for swiftTest.Shape.area.getter : Swift.Double in conformance swiftTest.Circle : swiftTest.Shape in swiftTest
0x0000000100004d40
就是Circle
中遵循协议并实现area中get
的函数地址
总结:
- 每个遵守了协议的类,都会有自己的PWT(Protocol Witness Table),遵守的协议函数越多,PWT中存储的函数地址就越多
- PWT的本质就是一个指针数组,第一个元素存储
ProtocolConformanceDescriptor
,其后面存储的是函数地址 - PWT的数量与协议数量一致
三.Existential Container
我们之前总结的LGProtocolBox
,就称为Existential Container
struct LGProtocolBox {
var valueBuffer1: UnsafeRawPointer
var valueBuffer2: UnsafeRawPointer
var valueBuffer3: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafePointer<TargetWitnessTable>
}
为什么要有Existential Container
?
//静态类型为Circle
var circle: Circle = Circle(10)
静态类型为Shape
var shape: Shape = Circle(10)
如果静态类型为具体的类型,那么编译器在编译的时候知道分配多大的内存空间来去存储变量。
但是静态类型为协议类型,那么编译器在编译的时候不知道分配多大的内存空间去存储。有可能是引用类型,有可能是值类型。因此使用Existential Container
作为中间层存储(40字节)。
Existential Container
是协议类型在编译过程中不确定的编译器技术。
Existential Container
为什么要存储metadata
?
对于我们Existential Container
也需要记录它的metadata
,需要记录它的真实类型信息。当我们调用属性或方法的时候才能找到metadata
。
1.小容量数据
小容量数据
大小不超过24字节的数据,直接存放在Value Buffer
Value Buffer
指的是Existential Container
前3个8字节
例如:Circle只有一个变量
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
var circle: Shape = Circle(10)
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102b8
0 elements
(lldb) x/8g 0x00000001000102b8
0x1000102b8: 0x4024000000000000 0x0000000000000000
0x1000102c8: 0x0000000000000000 0x000000010000c308
0x1000102d8: 0x000000010000c2f0 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
通过这里,可以看出第一个Value Buffer直接存放了10
(lldb) expr -f float -- 0x4024000000000000
(Int) $R1 = 10
(lldb)
*/
例如:Circle有3个变量(占满24字节)
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
var radious1 = 10
var radious2 = 10
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
var circle: Shape = Circle(10)
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102b8
0 elements
(lldb) x/8g 0x00000001000102b8
0x1000102b8: 0x4024000000000000 0x000000000000000a
0x1000102c8: 0x000000000000000a 0x000000010000c360
0x1000102d8: 0x000000010000c2f0 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
(lldb)
第一个Value Buffer直接存放了10.0
第二个Value Buffer直接存放了10
第三个Value Buffer直接存放了10
刚好使用完Value Buffer空间
*/
2.大容量数据
大容量数据
大小超过24字节的数据,通过堆区分配,存储堆空间地址
例如:我们之前观察的shape
,存放的是Heap Pointer
例如:Circle有4个变量(32字节)
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
var radious1 = 10
var radious2 = 10
var radious3 = 10
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
var circle: Shape = Circle(10)
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102c8
0 elements
(lldb) x/8g 0x00000001000102c8
0x1000102c8: 0x0000000101522120 0x0000000000000000
0x1000102d8: 0x0000000000000000 0x000000010000c388
0x1000102e8: 0x000000010000c318 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x0000000000000000
此时的Value Buffer1存放了开辟的堆空间地址
(lldb) x/8g 0x0000000101522120
0x101522120: 0x000000010000c300 0x0000000000000003
0x101522130: 0x4024000000000000 0x000000000000000a
0x101522140: 0x000000000000000a 0x000000000000000a
0x101522150: 0x0000000000000000 0x0000000000000000
0x0000000101522120 ---> 开辟的堆空间内存地址
除去metadata及refCount后,依次存储4条数据
(lldb) x/8g 0x000000010000c300
0x10000c300: 0x0000000000000400 0x0000000000000010
0x10000c310: 0x000000010000b944 0x000000010000a640
0x10000c320: 0x0000000100004de0 0x0000000100004e80
0x10000c330: 0x00000001000044e0 0x0000000100004eb0
这里的metadata指的是开辟的内存空间的metadata,此时类型为HeapLocalVariable(0x400)。
如果Circle是引用类型,堆空间的metadata和Existential Container中的metadata是一致的。
此时算小容量存储,将Heap Pointer存到Value Buffer1上。因此外部的metadata和内部的是一致的
(lldb) x/8g 0x000000010000c388
0x10000c388: 0x0000000000000200 0x000000010000a690
0x10000c398: 0x0000000800000000 0x0000001800000010
0x10000c3a8: 0x0000000100005d40 0x00000001000044e0
0x10000c3b8: 0x0000000100004730 0x0000000100004730
这里的metadata指的是遵循协议的metadata,也就是Circle的metadata。类型为Struct(0x200)
(lldb)
*/
总结:
-
Existential Container
是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协议类型,因为这些类型的内存大小不一致,所以通过当前的Existential Container
统一管理 - 对于小容量的数据,直接存储在
Value Buffer
- 对于大容量的数据,通过堆区分配,存储堆空间的地址
四.写时复制
当存放大容量的数据时,值类型数据会放入堆空间内存,如果此时去修改它,会发生什么变化?
protocol Shape {
var radious: Int { get set }
var radious1: Int { get set }
var radious2: Int { get set }
var radious3: Int { get set }
}
struct Circle: Shape {
var radious = 10
var radious1 = 20
var radious2 = 30
var radious3 = 40
}
var circle: Shape = Circle()
var circle1 = circle
/*
修改radious前的LLDB调试信息
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102e8
0 elements
(lldb) x/8g 0x00000001000102e8
0x1000102e8: 0x000000010b2c1b80 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x000000010000c3e0
0x100010308: 0x000000010000c318 0x000000010b2c1b80
0x100010318: 0x0000000000000000 0x0000000000000000
(lldb) po withUnsafePointer(to: &circle1) {print($0)}
0x0000000100010310
0 elements
(lldb) x/8g 0x0000000100010310
0x100010310: 0x000000010b2c1b80 0x0000000000000000
0x100010320: 0x0000000000000000 0x000000010000c3e0
0x100010330: 0x000000010000c318 0x0000000000000000
0x100010340: 0x0000000000000000 0x0000000000000000
(lldb)
此时发现circle和circle1的ValueBuffer中存储是同一个堆空间地址0x000000010b2c1b80
*/
circle.radious = 0
/*
修改radious后的LLDB调试信息
(lldb) x/8g 0x00000001000102e8
0x1000102e8: 0x0000000100706400 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x000000010000c3e0
0x100010308: 0x000000010000c318 0x000000010b2c1b80
0x100010318: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000100706400
0x100706400: 0x00007ff84223c1f8 0x0000000000000003
0x100706410: 0x0000000000000000 0x0000000000000014
0x100706420: 0x000000000000001e 0x0000000000000028
0x100706430: 0x0000000000000003 0x00007ff84223a3d8
(lldb)
(lldb) x/8g 0x0000000100010310
0x100010310: 0x000000010b2c1b80 0x0000000000000000
0x100010320: 0x0000000000000000 0x000000010000c3e0
0x100010330: 0x000000010000c318 0x0000000000000000
0x100010340: 0x0000000000000000 0x0000000000000000
(lldb)
此时发现,当修改了circle的radious后,circle中的堆空间地址发生了变化
相当于复制了一份到新的堆空间,然后修改了radious的值
*/
对于修改radious
后,复制了一块新的堆区空间来存储的现象,就称为写时复制
修改radious
的时候,会去判断堆空间的引用计数是是否为1
,如果大于1就会进行写时复制
,把当前堆区数据复制一份然后再修改radious
的值。
原理:对于协议类型,当值类型的值超过了Value Buffer
,会开辟了堆空间存储。系统在修改值的过程中,先去检测引用计数,如果引用计数大于1就会开辟内存空间,否则的话就不开辟。因为数据本身还是值类型,修改值的同时不能影响其它数据,不能表现为引用类型,也就是写时复制的原因。
好处:针对值类型,提高内存指针的利用率,降低堆区的内存的消耗,从而提高性能。
问题1:如果将Circle
改为class,会是什么情况?还会有写时复制吗?
首先要了解一个问题,为什么要写时复制。当Circle
是值类型时,此时circle1
修改了值,能影响circle
的值吗?答案是不能的。因此,在协议类型修改circle1
的值时,为了不影响circle
的值,所以有了写时复制,来保证原有值不会被修改。
那么我们再来分析如果circle1
和circle
引用类型,引用类型修改会影响其它一条数据,那么此时需要写时复制吗?肯定也不是需要的。所以对于引用类型来说,就没有写时复制的概念。
五.还原TargetProtocolMetadata
在源码中并未找到关于TargetProtocolMetadata
相关信息,这里由Mirror源码解析中介绍到的TargetProtocolMetadata
拓展研究得出的。
如果谁知道关于TargetProtocolMetadata
文档,希望告诉一下,谢谢。
//指针数组
//第一个元素ProtocolConformanceDescriptor,从二个开始存放的才是函数指针
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeMutablePointer<ProtocolConformanceDescriptor>
var witnessMethod: UnsafeRawPointer
//如果还有协议函数,跟在后面依次排列
}
//记录的是遵守协议的一些信息
struct ProtocolConformanceDescriptor {
var protocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
// Some description of the type that conforms to the protocol.
var typeRef: UnsafeRawPointer
// The witness table pattern, which may also serve as the witness table.
var witnessTablePattern: UnsafeRawPointer
// Various flags, including the kind of conformance.
//标志位
var flags: UInt32
}
//TargetProtocolDescriptor 继承自 TargetContextDescriptor
struct TargetProtocolDescriptor {
//TargetContextDescriptor中的数据
var flags: UInt32
var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
//协议名称
var name: TargetRelativeDirectPointer<CChar>
// The number of generic requirements in the requirement signature of the
// protocol.
var numRequirementsInSignature: UInt32
//需要遵循协议的数量
var numRequirements: UInt32
// Associated type names, as a space-separated list in the same order
// as the requirements.
//关联类型名称
var associatedTypeNames: TargetRelativeDirectPointer<CChar>
}
struct TargetProtocolMetadata {
var type: Any.Type
var witness_table: UnsafePointer<TargetWitnessTable>
}
//还原TargetProtocolMetadata
protocol MyProtocol {
func test()
}
struct LGTeacher: MyProtocol {
func test() {}
}
var type: MyProtocol.Type = LGTeacher.self
let protocolMetadata = unsafeBitCast(type, to: TargetProtocolMetadata.self)
print(protocolMetadata.type) //LGTeacher,遵循协议的类型
print(String(cString: protocolMetadata.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol
print(protocolMetadata.witness_table.pointee.witnessMethod) // 函数地址0x0000000100004cb0
/*
使用终端还原mach-o中的0x0000000100004cb0
❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004cb0
0000000100004cb0 t _$s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW
❯ xcrun swift-demangle s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW
$s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW ---> protocol witness for swiftTest.MyProtocol.test() -> () in conformance swiftTest.LGTeacher : swiftTest.MyProtocol in swiftTest
得出这个函数地址就是遵循MyProtocol协议的函数地址,也就是LGTeacher中的test函数地址
*/
六.问题探究
1.如果有一个类遵循了2个协议,那么Existential Container
里的witness table
里的数据是怎么存放的?
我们知道witness table
是一个指针数组,第一条数据存放的是协议信息,后续数据存放的是遵循协议的函数地址
protocol Myprotocol {
func test()
}
protocol MyProtocol1 {
func test1()
}
typealias CustomProtocol = Myprotocol & MyProtocol1
class LGTeacher: CustomProtocol {
func test(){
print("test")
}
func test1(){
print("test1")
}
}
var t: CustomProtocol = LGTeacher()
IR代码分析
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc %swift.metadata_response @"$s4main9LGTeacherCMa"(i64 0) #4
%4 = extractvalue %swift.metadata_response %3, 0
%5 = call swiftcc %T4main9LGTeacherC* @"$s4main9LGTeacherCACycfC"(%swift.type* swiftself %4)
//$s4main1tAA11MyProtocol1_AA10Myprotocolpvp ---> main.t : main.MyProtocol1 & main.Myprotocol
//存metadata到index1
store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 1), align 8
//$s4main9LGTeacherCAA11MyProtocol1AAWP ---> protocol witness table for main.LGTeacher : main.MyProtocol1 in main
//存Protocol1的协议见证表到index2
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9LGTeacherCAA11MyProtocol1AAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 2), align 8
//$s4main9LGTeacherCAA10MyprotocolAAWP ---> protocol witness table for main.LGTeacher : main.Myprotocol in main
//存Protocol1的协议见证表到inde3
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9LGTeacherCAA10MyprotocolAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 3), align 8
//将LGTeacher变量存入第一个元素
store %T4main9LGTeacherC* %5, %T4main9LGTeacherC** bitcast (%T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp" to %T4main9LGTeacherC**), align 8
ret i32 0
}
通过IR代码可以得出Existential Container
数据结构
struct LGProtocolBox {
//此时这里存放的是LGTeacher实例的Heap Pointer
var valueBuffer1: UnsafeRawPointer
//0x0,空闲
var valueBuffer2: UnsafeRawPointer
//0x0,空闲
var valueBuffer3: UnsafeRawPointer
//LGTeacher的metadata
var metadata: UnsafeRawPointer
//MyProtocol1的witness_table
var witness_table: UnsafePointer<TargetWitnessTable>
//MyProtocol的witness_table
var witness_table1: UnsafePointer<TargetWitnessTable>
}
注意哦,此时的Existential Container
大小就不再是40了,而是48
通过代码验证,当然这里也可以使用LLDB命令验证
protocol Myprotocol {
func test()
}
protocol MyProtocol1 {
func test1()
}
typealias CustomProtocol = Myprotocol & MyProtocol1
class LGTeacher: CustomProtocol {
func test(){}
func test1(){}
}
var t: CustomProtocol = LGTeacher()
//存在容器大小变为了48,每多一个协议,大小多8字节来存放witness_table
print(MemoryLayout.size(ofValue: t)) //48
let ptr = withUnsafePointer(to: &t) {
UnsafeRawPointer($0).assumingMemoryBound(to: LGProtocolBox.self)
}
print(String(cString: ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol1
print(String(cString: ptr.pointee.witness_table1.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // Myprotocol
print(ptr.pointee.witness_table.pointee.witnessMethod) //0x0000000100004bb0
print(ptr.pointee.witness_table1.pointee.witnessMethod) //0x0000000100004b90
/*
❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004bb0
0000000100004bb0 t _$s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW
❯ xcrun swift-demangle s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW
$s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW ---> protocol witness for swiftTest.MyProtocol1.test1() -> () in conformance swiftTest.LGTeacher : swiftTest.MyProtocol1 in swiftTest
❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004b90
0000000100004b90 t _$s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW
❯ xcrun swift-demangle s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW
$s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW ---> protocol witness for swiftTest.Myprotocol.test() -> () in conformance swiftTest.LGTeacher : swiftTest.Myprotocol in swiftTest
*/
总结:
Existential Container
大小并不是固定是40字节的,当多协议类型时,Existential Container
会扩容,每多一个协议会扩容8字节来存入PWT
。
2.如果有一个类遵循了2个协议,还原一下协议的metadata
其实逻辑与Existential Container
一致,多了一个协议多了一个witness_table
,那么在Metadata
中也会多一个witness_table
。
此时TargetProtocolMetadata
数据结构
struct TargetProtocolMetadata {
var type: Any.Type
var witness_table: UnsafePointer<TargetWitnessTable>
var witness_table1: UnsafePointer<TargetWitnessTable>
}
protocol Myprotocol {
func test()
}
protocol MyProtocol1 {
func test1()
}
typealias CustomProtocol = Myprotocol & MyProtocol1
class LGTeacher: CustomProtocol {
func test(){}
func test1(){}
}
var type: CustomProtocol.Type = LGTeacher.self
let ptr = unsafeBitCast(type, to: TargetProtocolMetadata.self)
print(ptr.type) //LGTeacher,遵循协议的类型
print(String(cString: ptr.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol1
//函数地址就不一一还原了,这里肯定就是LGTeacher实现协议MyProtocol1中test函数的地址
print(ptr.witness_table.pointee.witnessMethod) // 函数地址0x0000000100004af0
print(String(cString: ptr.witness_table1.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // Myprotocol
print(ptr.witness_table1.pointee.witnessMethod) // 函数地址0x0000000100004ad0