Swift探索( 八): 协议

一:协议

1.1 协议的定义

协议可以用来定义 方法属性下标的声明 ,协议可以被 枚举结构体遵守(多个协议之间用逗号隔开)

1.2 协议的基本语法

  • 协议属性要求:必须明确是 getgetset ,且必须是变量用 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
}

拉到最后 可以看到对应 PersonBoy 两个类来说 都有自己的 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源码解析 文章中我们得知了 EnumStructClass 都有自己的 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

我们可以发现 p1p2 的静态变量类型不一样,这里它的内存大小就不一致,也就是说确定类型变量和协议变量的大小是不同的,这也就说明了两个实例在底层的数据结构是不同的。接下来看一下里面的具体内容

3.1 p1: Person 的内存分析

p1: Person的LLDB调试.png

因为之前打印出 p1 的内存大小就只有 8 字节,所以这里只需要看 p1 指针的内存地址 0x0000000100008210 存储的前面的 8 字节的内容 0x000000010112eb90,这个地址其实就是实例对象在堆空间的内存地址。 这里通过 LLDB 命令 expr -f float -- 0x40672000000000000x4067200000000000 这个地址进行浮点数的还原。

3.2 p2: Myprotocol 的内存分析

p2: Myprotocol的LLDB调试.png

因为之前打印出 p2 的内存大小是 40 字节大小,所以查看前 5 块。

  • 第一个 8 字节跟 p1 是一样的存储的也是实例对象在堆空间的内存地址。
  • 第二、三个 8 字节不知道是什么。
    在第一篇文章中 Swift探索(一): 类与结构体(上) 我们就分析出 Swift 对象内存结构为 HeapObjectmetadatarefCount 组成。
  • 这里第四个 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 代码来看看能不能还原出 unknown1unknown2unknown3

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 在最上面可以看到 T4main10MyprotocolPswift.type 类型,这里的意思就是将 %4 也就是 Personmetadata 存储到协议结构体中的第二个元素。

  • 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 代码来分析 protocolConformanceDescriptorprotocolMethod 这两个就很吃力了于是直接去 Swift源码 查看。直接搜索 TargetWitnessTableMetadata.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源码解析 也分析过里面有两个属性 flagsparent 其中 flagsUInt32 类型, parent 也是一个相对指针。 TargetProtocolDescriptor 里的四个属性中 NameAssociatedTypeNames 也是相对指针。
TargetProtocolConformanceDescriptor 中的 typeRefwitnessTablePattern 我们这里就不还原了就直接定义成 UnsafeRawPointerflagConformanceFlags 类型的进入 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 0x00000001000029d0Mach-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个字节分别存储的 Personheightweightweight1 的值。这里再添加一个属性 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 的两个特点。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Protocol:所谓协议,就是一组属性和/或方法的定义,而如果某个具体类型想要遵守一个协议,那它需要实现这个协议...
    帅驼驼阅读 527评论 4 3
  • Swift — 协议(Protocol) [TOC] 协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法...
    just东东阅读 1,957评论 1 3
  • 协议方法的调度 protocol Incrementable{ var age: Int {set get} ...
    张天宇_bba7阅读 199评论 0 2
  • 前言 本篇文章主要讲解Swift中常用的协议Protocol,主要分析protocol的用法及底层存储结构。 一、...
    深圳_你要的昵称阅读 1,208评论 0 6
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,826评论 0 6