一:协议
1.1 协议的定义
协议可以用来定义 方法、属性 、下标的声明 ,协议可以被 枚举、结构体、类遵守(多个协议之间用逗号隔开)
1.2 协议的基本语法
- 协议属性要求:必须明确是
get或get和set,且必须是变量用var进行修饰,并不是说当前声明get的属性一定是计算属性。
protocol MyProtocol {
var age: Int{ get set }
var name: String{ get }
}
class Person: MyProtocol{
var age: Int = 18
var name: String
init(_ name: String) {
self.name = name
}
}
- 协议中的异变方法:表示在该方法可以改变其所属的实例,以及该实例的所有属性(用于枚举和结构体),在为类实现该方法的时候不需要写
mutating关键字
protocol MyProtocol {
mutating func test()
}
class PClass: MyProtocol {
func test() {
print("test")
}
}
struct PStruct: MyProtocol {
mutating func test() {
print("test")
}
}
- 协议构造器:类在实现协议中的初始化器,必须使用
required关键字修饰初始化器的实现(类的初始化器前添加required修饰符来表明所有该类的子类都必须实现该初始化器
protocol MyProtocol {
init(_ age: Int)
}
class Person: MyProtocol {
var age = 10
required init(_ age: Int) {
self.age = age
}
}
- 类专用协议:通过添加
AnyObject关键字到协议的继承列表,就可以限制协议只能被类类型采纳
protocol MyProtocol: AnyObject{}
- 可选协议:如果我们不想强制让遵循协议的类类型实现,可以使用
optional作为前缀放在协议的定义,optional修饰符仅仅可以用于使用objc特性标记过的协议。
@objc protocol MyProtocol{
@objc optional func test()
}
- 协议扩展:协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
protocol MyProtocol{
func test()
}
extension MyProtocol {
func test() {
print("test")
}
}
二: 协议方法的调用
2.1 协议方法的调度原理
在 Swift探索(二): 类与结构体(下) 中我们了解到类的方法调度是通过函数表 V-Table 的方式。那么加上协议之后,协议中的方法又是怎样调度的呢?
protocol MyProtocol{
func test(add: Int)
}
class Person: MyProtocol {
var age = 10
func test(add: Int) {
age += add
print(age)
}
}
var p: Person = Person.init()
p.test(add: 5)
编译成 SIL 代码找到 main 函数的调用
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1pAA6PersonCvp // id: %2
%3 = global_addr @$s4main1pAA6PersonCvp : $*Person // users: %8, %7
%4 = metatype $@thick Person.Type // user: %6
// function_ref Person.__allocating_init()
%5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick Person.Type) -> @owned Person // user: %7
store %6 to [init] %3 : $*Person // id: %7
%8 = begin_access [read] [dynamic] %3 : $*Person // users: %10, %9
%9 = load [copy] %8 : $*Person // users: %17, %16, %15
end_access %8 : $*Person // id: %10
%11 = integer_literal $Builtin.IntLiteral, 5 // user: %14
%12 = metatype $@thin Int.Type // user: %14
// function_ref Int.init(_builtinIntegerLiteral:)
%13 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %14
%14 = apply %13(%11, %12) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %16
%15 = class_method %9 : $Person, #Person.test : (Person) -> (Int) -> (), $@convention(method) (Int, @guaranteed Person) -> () // user: %16
%16 = apply %15(%14, %9) : $@convention(method) (Int, @guaranteed Person) -> ()
destroy_value %9 : $Person // id: %17
%18 = integer_literal $Builtin.Int32, 0 // user: %19
%19 = struct $Int32 (%18 : $Builtin.Int32) // user: %20
return %19 : $Int32 // id: %20
} // end sil function 'main'
可以看到 %15 = class_method %9 : $Person, #Person.test : (Person) -> (Int) -> (), $@convention(method) (Int, @guaranteed Person) -> () // user: %16 协议的函数 test() 是通过 class_method 的方式调用,在 SIL官方文档 中找到 class_method
class_method官方声明.png
在 SIL 代码的最后可以看到 Person 类的 V-Table
Person类的V-Table.png
我们可以看到下面还有个
sil_witness_table我们将上述代码中的
var p: Person = Person.init() 改成 var p: MyProtocol = Person.init() 在编译成 SIL 代码并找到 main 函数的调用
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1pAA10MyProtocol_pvp // id: %2
%3 = global_addr @$s4main1pAA10MyProtocol_pvp : $*MyProtocol // users: %9, %7
%4 = metatype $@thick Person.Type // user: %6
// function_ref Person.__allocating_init()
%5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick Person.Type) -> @owned Person // user: %8
%7 = init_existential_addr %3 : $*MyProtocol, $Person // user: %8
store %6 to [init] %7 : $*Person // id: %8
%9 = begin_access [read] [dynamic] %3 : $*MyProtocol // users: %12, %11
%10 = alloc_stack $MyProtocol // users: %21, %20, %13, %11
copy_addr %9 to [initialization] %10 : $*MyProtocol // id: %11
end_access %9 : $*MyProtocol // id: %12
%13 = open_existential_addr immutable_access %10 : $*MyProtocol to $*@opened("E387E5CE-C3D3-11EC-96FC-ACDE48001122") MyProtocol // users: %19, %19, %18
%14 = integer_literal $Builtin.IntLiteral, 5 // user: %17
%15 = metatype $@thin Int.Type // user: %17
// function_ref Int.init(_builtinIntegerLiteral:)
%16 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %17
%17 = apply %16(%14, %15) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %19
%18 = witness_method $@opened("E387E5CE-C3D3-11EC-96FC-ACDE48001122") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> (Int) -> (), %13 : $*@opened("E387E5CE-C3D3-11EC-96FC-ACDE48001122") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (Int, @in_guaranteed τ_0_0) -> () // type-defs: %13; user: %19
%19 = apply %18<@opened("E387E5CE-C3D3-11EC-96FC-ACDE48001122") MyProtocol>(%17, %13) : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (Int, @in_guaranteed τ_0_0) -> () // type-defs: %13
destroy_addr %10 : $*MyProtocol // id: %20
dealloc_stack %10 : $*MyProtocol // id: %21
%22 = integer_literal $Builtin.Int32, 0 // user: %23
%23 = struct $Int32 (%22 : $Builtin.Int32) // user: %24
return %23 : $Int32 // id: %24
} // end sil function 'main'
%18 = witness_method $@opened("E387E5CE-C3D3-11EC-96FC-ACDE48001122") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> (Int) -> (), %13 : 我们可以看到这里之前的 class_method 变成了 witness_method,我们还是去 SIL官方文档 中找到 witness_method
witness_method官方声明..png
witness_method 其实就是要去协议见证表 sil_witness_table 去查找所需要的方法。sil_witness_table 其实就是记录着类实现这个协议的方法的编码信息在
SIL 代码中找到 sil_witness_table
sil_witness_table hidden Person: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> (Int) -> () : @$s4main6PersonCAA10MyProtocolA2aDP4test3addySi_tFTW // protocol witness for MyProtocol.test(add:) in conformance Person
}
搜索一下 s4main6PersonCAA10MyProtocolA2aDP4test3addySi_tFTW
// protocol witness for MyProtocol.test(add:) in conformance Person
sil private [transparent] [thunk] [ossa] @$s4main6PersonCAA10MyProtocolA2aDP4test3addySi_tFTW : $@convention(witness_method: MyProtocol) (Int, @in_guaranteed Person) -> () {
// %0 // user: %4
// %1 // user: %2
bb0(%0 : $Int, %1 : $*Person):
%2 = load_borrow %1 : $*Person // users: %6, %4, %3
%3 = class_method %2 : $Person, #Person.test : (Person) -> (Int) -> (), $@convention(method) (Int, @guaranteed Person) -> () // user: %4
%4 = apply %3(%0, %2) : $@convention(method) (Int, @guaranteed Person) -> ()
%5 = tuple () // user: %7
end_borrow %2 : $Person // id: %6
return %5 : $() // id: %7
} // end sil function '$s4main6PersonCAA10MyProtocolA2aDP4test3addySi_tFTW'
这里又是 class_method 也就是说这里是通过 witness_table 进行了一次桥接,最终找到实例变量的具体类型的具体实现。
通过以上逻辑我们可以得出一下结论:
- 如果实例对象的静态类型是 具体的类型,那么这个协议方法通过
V-Table进行调度。- 如果实例对象的静态类型是 协议类型,那么这个协议方法通过
witness_table(协议见证表) 中对应的协议方法,然后通过协议方法去查找这个对象的动态类型的具体实现 (V-Table) 进行调度。
2.2 多个类准守协议的情况下的 witness_table
protocol MyProtocol{
func test()
}
class Person: MyProtocol {
func test() {
print("Person")
}
}
class Boy: MyProtocol {
func test() {
print("Boy")
}
}
还是编译成 SIL 代码来查看
sil_vtable Person {
#Person.test: (Person) -> () -> () : @$s4main6PersonC4testyyF // Person.test()
#Person.init!allocator: (Person.Type) -> () -> Person : @$s4main6PersonCACycfC // Person.__allocating_init()
#Person.deinit!deallocator: @$s4main6PersonCfD // Person.__deallocating_deinit
}
sil_vtable Boy {
#Boy.test: (Boy) -> () -> () : @$s4main3BoyC4testyyF // Boy.test()
#Boy.init!allocator: (Boy.Type) -> () -> Boy : @$s4main3BoyCACycfC // Boy.__allocating_init()
#Boy.deinit!deallocator: @$s4main3BoyCfD // Boy.__deallocating_deinit
}
sil_witness_table hidden Person: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main6PersonCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance Person
}
sil_witness_table hidden Boy: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main3BoyCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance Boy
}
拉到最后 可以看到对应 Person 和 Boy 两个类来说 都有自己的 witness_table
2.3 继承情况下的 witness_table
protocol MyProtocol{
func test()
}
class Person: MyProtocol {
func test() {
print("Person")
}
}
class Boy: Person {
}
class Girl: Person {
override func test() {
print("Girl")
}
}
其中 Person 类准守了 MyProtocol 协议, Girl 类继承自 Person , 编译成 SIL
sil_vtable Person {
#Person.test: (Person) -> () -> () : @$s4main6PersonC4testyyF // Person.test()
#Person.init!allocator: (Person.Type) -> () -> Person : @$s4main6PersonCACycfC // Person.__allocating_init()
#Person.deinit!deallocator: @$s4main6PersonCfD // Person.__deallocating_deinit
}
sil_vtable Boy {
#Person.test: (Person) -> () -> () : @$s4main6PersonC4testyyF [inherited] // Person.test()
#Person.init!allocator: (Person.Type) -> () -> Person : @$s4main3BoyCACycfC [override] // Boy.__allocating_init()
#Boy.deinit!deallocator: @$s4main3BoyCfD // Boy.__deallocating_deinit
}
sil_vtable Girl {
#Person.test: (Person) -> () -> () : @$s4main4GirlC4testyyF [override] // Girl.test()
#Person.init!allocator: (Person.Type) -> () -> Person : @$s4main4GirlCACycfC [override] // Girl.__allocating_init()
#Girl.deinit!deallocator: @$s4main4GirlCfD // Girl.__deallocating_deinit
}
sil_witness_table hidden Person: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main6PersonCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance Person
}
可以看到子类不管是否重写父类方法中的协议方法时都是没有 witness_table 的。他们共用父类的 witness_table。
2.4 准守多个协议情况下的 witness_table
protocol MyProtocol{
func test()
}
protocol MyProtocol1{
func test1()
}
protocol MyProtocol2{
func test2()
}
class Person: MyProtocol, MyProtocol1, MyProtocol2 {
func test() {
print("test")
}
func test1() {
print("test1")
}
func test2() {
print("test2")
}
}
这里 Person 准守了三个协议,编译成 SIL 代码
sil_vtable Person {
#Person.test: (Person) -> () -> () : @$s4main6PersonC4testyyF // Person.test()
#Person.test1: (Person) -> () -> () : @$s4main6PersonC5test1yyF // Person.test1()
#Person.test2: (Person) -> () -> () : @$s4main6PersonC5test2yyF // Person.test2()
#Person.init!allocator: (Person.Type) -> () -> Person : @$s4main6PersonCACycfC // Person.__allocating_init()
#Person.deinit!deallocator: @$s4main6PersonCfD // Person.__deallocating_deinit
}
sil_witness_table hidden Person: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main6PersonCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance Person
}
sil_witness_table hidden Person: MyProtocol1 module main {
method #MyProtocol1.test1: <Self where Self : MyProtocol1> (Self) -> () -> () : @$s4main6PersonCAA11MyProtocol1A2aDP5test1yyFTW // protocol witness for MyProtocol1.test1() in conformance Person
}
sil_witness_table hidden Person: MyProtocol2 module main {
method #MyProtocol2.test2: <Self where Self : MyProtocol2> (Self) -> () -> () : @$s4main6PersonCAA11MyProtocol2A2aDP5test2yyFTW // protocol witness for MyProtocol2.test2() in conformance Person
}
可以看到有三个 witness_table
由上可以总结出:
-
当一个协议被多个类遵守的时候,那么在各自类中都会有一个
witness_table。 -
如果一个类遵守了一个协议,这个类必然会有一个
witness_table,那么这个类的子类和父类共用一份witness_table。 -
当一个类遵守多个协议的时候,那么在这个类中,有每个协议对应的
witness_table,也就是会有多个witness_table,这个取决于协议的数量。
三:协议的本质
在前面的 Swift探索(六): Mirror源码解析 文章中我们得知了 Enum 、 Struct 、 Class 都有自己的 Metadata ,并且 Metadata 里都有 typeDescriptor ,那么协议的本质是怎样的呢?先来看一下协议类型的大小
protocol Myprotocol {
var age: Int {
get
}
}
class Person: Myprotocol {
var height: Double
init(_ height: Double) {
self.height = height
}
var age: Int {
get {
return 18
}
}
}
var p1: Person = Person.init(185.0)
var p1Type = type(of: p1)
print(class_getInstanceSize(p1Type as? AnyClass))
print(MemoryLayout.size(ofValue: p1))
var p2: Myprotocol = Person.init(185.0)
var p2Type = type(of: p2)
print(class_getInstanceSize(p2Type as? AnyClass))
print(MemoryLayout.size(ofValue: p2))
打印结果:
24
8
24
40
我们可以发现 p1 和 p2 的静态变量类型不一样,这里它的内存大小就不一致,也就是说确定类型变量和协议变量的大小是不同的,这也就说明了两个实例在底层的数据结构是不同的。接下来看一下里面的具体内容
3.1 p1: Person 的内存分析
p1: Person的LLDB调试.png
因为之前打印出
p1 的内存大小就只有 8 字节,所以这里只需要看 p1 指针的内存地址 0x0000000100008210 存储的前面的 8 字节的内容 0x000000010112eb90,这个地址其实就是实例对象在堆空间的内存地址。 这里通过 LLDB 命令 expr -f float -- 0x4067200000000000 对 0x4067200000000000 这个地址进行浮点数的还原。
3.2 p2: Myprotocol 的内存分析
p2: Myprotocol的LLDB调试.png
因为之前打印出
p2 的内存大小是 40 字节大小,所以查看前 5 块。
- 第一个
8字节跟p1是一样的存储的也是实例对象在堆空间的内存地址。 - 第二、三个
8字节不知道是什么。
在第一篇文章中 Swift探索(一): 类与结构体(上) 我们就分析出Swift对象内存结构为HeapObject由metadata和refCount组成。 - 这里第四个
8字节存储的和下面的第一个8字节一样,因此这里存储的是metadata。 - 第五个
8字节不知道是什么。
由上可以得出协议对象的数据结构为
struct ProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var unknow3: UnsafeRawPointer
}
3.3 通过 IR 代码还原
接着通过 IR 代码来看看能不能还原出 unknown1 、 unknown2 、 unknown3
protocol Myprotocol {
var age: Int {
get
}
}
class Person: Myprotocol {
var height: Double
init(_ height: Double) {
self.height = height
}
var age: Int {
get {
return 18
}
}
}
var p2: Myprotocol = Person.init(185.0)
编译成 IR 代码,定位到 main 函数
%T4main10MyprotocolP = type { [24 x i8], %swift.type*, i8** }
%swift.type = type { i64 }
%T4main6PersonC = type <{ %swift.refcounted, %TSd }>
%swift.refcounted = type { %swift.type*, i64 }
%TSd = type <{ double }>
···
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc %swift.metadata_response @"$s4main6PersonCMa"(i64 0) #7
%4 = extractvalue %swift.metadata_response %3, 0
%5 = call swiftcc %T4main6PersonC* @"$s4main6PersonCyACSdcfC"(double 1.850000e+02, %swift.type* swiftself %4)
store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main10MyprotocolP, %T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp", i32 0, i32 1), align 8
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6PersonCAA10MyprotocolAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main10MyprotocolP, %T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp", i32 0, i32 2), align 8
store %T4main6PersonC* %5, %T4main6PersonC** bitcast (%T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp" to %T4main6PersonC**), align 8
ret i32 0
}
-
%3 = call swiftcc %swift.metadata_response @"$s4main6PersonCMa"(i64 0) #7通过xcrun swift-demangle s4main6PersonCMa命令可以得到$s4main6PersonCMa ---> type metadata accessor for main.Person也就是说这里是获取Person类的metadata -
%4 = extractvalue %swift.metadata_response %3, 0将获取到的metadata存储到%4中 -
%5 = call swiftcc %T4main6PersonC* @"$s4main6PersonCyACSdcfC"(double 1.850000e+02, %swift.type* swiftself %4)调用函数 这里在IR中全局搜索一下
define hidden swiftcc %T4main6PersonC* @"$s4main6PersonCyACSdcfC"(double %0, %swift.type* swiftself %1) #0 {
entry:
%2 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* %1, i64 24, i64 7) #4
%3 = bitcast %swift.refcounted* %2 to %T4main6PersonC*
%4 = call swiftcc %T4main6PersonC* @"$s4main6PersonCyACSdcfc"(double %0, %T4main6PersonC* swiftself %3)
ret %T4main6PersonC* %4
}
可以看到这里其实就是调用 Person 类的 __allocating_init 初始化方法,通过xcrun swift-demangle s4main6PersonCyACSdcfC 命令可以得到 $s4main6PersonCyACSdcfC ---> main.Person.__allocating_init(Swift.Double) -> main.Person 也可以说明这里是创建一个实例对象赋值给 %5
-
store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main10MyprotocolP, %T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp", i32 0, i32 1), align 8在上一篇 Swift探索(七): 闭包 中就了解过getelementptr指令,这里也同样的通过xcrun swift -demangle s4main2p2AA10Myprotocol_pvp还原一下得到$s4main2p2AA10Myprotocol_pvp ---> main.p2 : main.Myprotocol在最上面可以看到T4main10MyprotocolP和swift.type类型,这里的意思就是将%4也就是Person的metadata存储到协议结构体中的第二个元素。 -
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6PersonCAA10MyprotocolAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main10MyprotocolP, %T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp", i32 0, i32 2), align 8还是先还原s4main6PersonCAA10MyprotocolAAWP得到$s4main6PersonCAA10MyprotocolAAWP ---> protocol witness table for main.Person : main.Myprotocol in main我们可以理解为这里的意思是将witness_table存储到协议结构体中的第三个元素。根据上面的内存分析,那么我们就可以得到unknow3就是witness_table -
store %T4main6PersonC* %5, %T4main6PersonC** bitcast (%T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp" to %T4main6PersonC**), align 8将实例对象地址存储到 第一个元素这里的,通过最上面的信息得知T4main6PersonC的是一个{ %swift.refcounted, double }在上一篇文章中我们就分析过swift.refcounted所以第一个元素就是[heapObject, unkown1, unkown2]
综上我们得到了unkown3就是witness_table这里面具体存的是什么东西呢?还是来看store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6PersonCAA10MyprotocolAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main10MyprotocolP, %T4main10MyprotocolP* @"$s4main2p2AA10Myprotocol_pvp", i32 0, i32 2), align 8这句代码,可以看到这里是一个[2 x i8*]类型的数组,定位到s4main6CircleCAA5ShapeAAWP的声明 -
@"$s4main6PersonCAA10MyprotocolAAWP" = hidden constant [2 x i8*] [i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main6PersonCAA10MyprotocolAAMc" to i8*), i8* bitcast (i64 (%T4main6PersonC**, %swift.type*, i8**)* @"$s4main6PersonCAA10MyprotocolA2aDP3ageSivgTW" to i8*)], align 8
还原s4main6PersonCAA10MyprotocolAAMc得到$s4main6PersonCAA10MyprotocolAAMc ---> protocol conformance descriptor for main.Person : main.Myprotocol in main, 还原s4main6PersonCAA10MyprotocolA2aDP3ageSivgTW得到$s4main6PersonCAA10MyprotocolA2aDP3ageSivgTW ---> protocol witness for main.Myprotocol.age.getter : Swift.Int in conformance main.Person : main.Myprotocol in main
由此可以得到witness_table是一个数组,第一个元素是protocol_conformance_descriptor,第二个元素是实现协议的内容。因此可以还原出witness_table
struct ProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witnessTable: UnsafeMutablePointer<TargetWitnessTable>
}
// 使用结构体代替数组,因为在存储上两者是一致的
struct TargetWitnessTable {
var protocolConformanceDescriptor: UnsafeRawPointer
var protocolMethod: UnsafeRawPointer //协议见证里的方法的起始地址
// 如果实现了协议的多个方法,则在后面加成员
}
3.4 源码还原 protocolConformanceDescriptor
这里我们再通过 IR 代码来分析 protocolConformanceDescriptor 和 protocolMethod 这两个就很吃力了于是直接去 Swift源码 查看。直接搜索 TargetWitnessTable 在 Metadata.h 文件中找到如下代码
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;
}
};
进入 TargetProtocolConformanceDescriptor
struct TargetProtocolConformanceDescriptor final
: public swift::ABI::TrailingObjects<
TargetProtocolConformanceDescriptor<Runtime>,
TargetRelativeContextPointer<Runtime>,
TargetGenericRequirementDescriptor<Runtime>,
TargetResilientWitnessesHeader<Runtime>,
TargetResilientWitness<Runtime>,
TargetGenericWitnessTable<Runtime>> {
...
private:
/// 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;
...
};
我们可以看到 TargetProtocolConformanceDescriptor 有四个属性于是可以还原出
struct TargetProtocolConformanceDescriptor {
var protocolDesc
var typeRef
var witnessTablePattern
var flags
}
其中 Protocol 是一个相对类型的指针,在 Swift探索(六): Mirror源码解析 中我们已经还原过 TargetRelativeContextPointer 接着进入 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;
...
};
可以看到 TargetProtocolDescriptor 继承自 TargetContextDescriptor 并且有四个属性。 TargetContextDescriptor 这个我们在 Swift探索(六): Mirror源码解析 也分析过里面有两个属性 flags 和 parent 其中 flags 是 UInt32 类型, parent 也是一个相对指针。 TargetProtocolDescriptor 里的四个属性中 Name 和 AssociatedTypeNames 也是相对指针。
TargetProtocolConformanceDescriptor 中的 typeRef 和 witnessTablePattern 我们这里就不还原了就直接定义成 UnsafeRawPointer, flag 是 ConformanceFlags 类型的进入 ConformanceFlags
class ConformanceFlags {
public:
typedef uint32_t int_type;
...
};
可以看见 ConformanceFlags 其实就是一个 UInt32 于是我们就还原出
struct ProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witnessTable: UnsafeMutablePointer<TargetWitnessTable>
}
// 使用结构体代替数组,因为在存储上两者是一致的
struct TargetWitnessTable {
var protocolConformanceDescriptor: UnsafeMutablePointer<TargetProtocolConformanceDescriptor>
var protocolMethod: 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>
}
// 传入指针
struct TargetRelativeDirectPointer<Pointee>{
var offset: Int32
mutating func getApplyRelativeOffset() -> UnsafeMutablePointer<Pointee>{
let offset = self.offset
return withUnsafePointer(to: &self) { p in
// 获取指针地址 偏移offset后 重新绑定为传入的指针的类型
let pointer = UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self)
return UnsafeMutablePointer(mutating: pointer)
}
}
}
接下来我们验证下
protocol Myprotocol {
var age: Int {
get
}
}
class Person: Myprotocol {
var height: Double
init(_ height: Double) {
self.height = height
}
var age: Int {
get {
return 18
}
}
}
var p: Myprotocol = Person.init(185.0)
// 拿到circle1堆区地址,然后内存绑定ProtocolBox类型
let personPtr = withUnsafePointer(to: &p) { ptr in
return ptr.withMemoryRebound(to: ProtocolBox.self, capacity: 1) { pointer in
return pointer
}
}
let desc = personPtr.pointee.witnessTable.pointee.protocolConformanceDescriptor.pointee.protocolDesc.getApplyRelativeOffset()
print(String(cString: desc.pointee.name.getApplyRelativeOffset()))
print(personPtr.pointee.witnessTable.pointee.protocolMethod)
// 打印结果
Myprotocol
0x00000001000029d0
打印出 protocolMethod 的地址是 0x00000001000029d0 通过一下两个命令
-
nm -p 可执行文件的路径 | grep 0x00000001000029d0在Mach-O文件打印这个地址的符号 -
xcrun swift-demangle 打印出来时符号转换符号
image.png
还原出来可以发现这个地址就是Person类实现协议Myprotocol中的age.getter
四:Existential Container -- 存在容器
Existential Container 是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协议类型,因为这些类型的内存大小不一致,所以通过当前的 Existential Container 统一管理。对于 Existential Container 还有以下两个特点
-
对于小容量的数据,直接存储在
Value Buffer -
对于大容量的数据,通过堆区分配,存储堆空间的地址
在第 3 点中我们分析出协议对象的数据结构是一个 ProtocolBox
struct ProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witnessTable: UnsafeMutablePointer<TargetWitnessTable>
}
这里的 ProtocolBox 其实就是 Existential Container 存在容器。
这个存在容器最后的两个 8 字节存储的内容是固定的,存储的是这个实例类型的元类型 ( metadata )和协议的见证表 ( witnessTable ) 。
前面的 24 个字节用来存放什么:
-
如果这个实例的动态类型是 引用类型,那么第一个
8字节存储的就是实例在堆空间的地址值 (heapObject)。 -
如果这个实例的动态类型是 值类型 :
- 当这
24个字节可以完全存储值类型的内存(也就是值类型的属性值),那么它就直接存储在这24个字节里。 - 如果超出了
24个字节,会通过堆区分配,然后第一个8字节存储堆空间的地址。
- 当这
对于这个实例的动态类型是 值类型 时 ProtocolBox 的数据结构应该变成
struct ProtocolBox {
var valuerBuffer1: UnsafeRawPointer
var valuerBuffer2: UnsafeRawPointer
var valuerBuffer3: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witnessTable: UnsafeMutablePointer<TargetWitnessTable>
}
接下来验证一下,将之前的例子当中 class Person 变成 struct Person
protocol Myprotocol {
var age: Int {
get
}
}
struct Person: Myprotocol {
var height: Double
var weight: Double = 125.5
var weight1: Double = 135.5
init(_ height: Double) {
self.height = height
}
var age: Int {
get {
return 18
}
}
}
var pStruct: Myprotocol = Person.init(185.0)
print("end")
小容量的数据.png
通过
expr -f float -- <地址> 命令可以看到这里的前24个字节分别存储的 Person 的 height、weight 和 weight1 的值。这里再添加一个属性 weight2
protocol Myprotocol {
var age: Int {
get
}
}
struct Person: Myprotocol {
var height: Double
var weight: Double = 125.5
var weight1: Double = 135.5
init(_ height: Double) {
self.height = height
}
var age: Int {
get {
return 18
}
}
}
var pStruct: Myprotocol = Person.init(185.0)
print("end")
大容量的数据.png
我们可以看到这时前
24 字节已经存储不下这些属性值,这时就要在堆区开辟空间,并且将这块堆空间的地址存储在前 8 字节中。通过这个案例就验证了上面对于 Existential Container 的两个特点。









网友评论