Swift探索

Swift探索( 八): 协议

2022-05-16  本文已影响0人  Lee_Toto

一:协议

1.1 协议的定义

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

1.2 协议的基本语法

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
    }
}
protocol MyProtocol {
    mutating func test()
}

class PClass: MyProtocol {
    func test() {
        print("test")
    }
}

struct PStruct: MyProtocol {
    mutating func test() {
        print("test")
    }
}
protocol MyProtocol {
    init(_ age: Int)
}

class Person: MyProtocol {
    var age = 10
    required init(_ age: Int) {
        self.age = age
    }
}
protocol MyProtocol: AnyObject{}
@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
由上可以总结出:

三:协议的本质

在前面的 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 块。
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
}
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

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 通过一下两个命令

四:Existential Container -- 存在容器

Existential Container 是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协议类型,因为这些类型的内存大小不一致,所以通过当前的 Existential Container 统一管理。对于 Existential Container 还有以下两个特点

在第 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 个字节用来存放什么:

对于这个实例的动态类型是 值类型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 的两个特点。
上一篇下一篇

猜你喜欢

热点阅读