美文网首页Swift探索
Swift探索( 八): 协议

Swift探索( 八): 协议

作者: Lee_Toto | 来源:发表于2022-05-16 23:04 被阅读0次

    一:协议

    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 的两个特点。

    相关文章

      网友评论

        本文标题:Swift探索( 八): 协议

        本文链接:https://www.haomeiwen.com/subject/ibiwertx.html