一:协议
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
在 SIL
代码的最后可以看到 Person
类的 V-Table
我们可以看到下面还有个
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
其实就是要去协议见证表 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
的两个特点。
网友评论