Swift -- 9.协议

一.协议与继承

class LGTeacher{
    var age = 10
    var name = "Kody"
}
class Dog{
    var name = "糯米"
    var type = "白梗"
}

/*
 这里有2个类,LGTeacher与Dog,此时想为2个类添加debug函数去打印类相关的信息
 
 从继承的角度上看,我们会封装一个Animal类(公共的基类)。从业务逻辑来说,这么处理不太合适
  */

那么最直观也是最简单的办法就是,给每一个类添加一个debug函数

class LGTeacher{
    var age = 10
    var name = "Kody"
    
    func debug() {
        print(...)
    }
}
class Dog{
    var name = "糯米"
    var type = "白梗"
    
    func debug() {
        print(...)
    }
}

如果我们对当前代码中的每个类都需要添加debug函数,显然上面这种方法是行不通的,于是有了下面的代码

func debug(subject: Any){
    print(.....)
}

当然看到这里可能会觉得没有问题,如果我们想要描述当前类的具体信息,这个时候我们还需要引入一个公共的基类,同时我们还需要有一个公共的属性description来让子类重载,这无疑是对我们的代码是很强的入侵

所以这个时候我们通过协议来描述当前类的具体行为,并通过extension的方式来对我们的类进行扩展,这无疑是最好的办法

extension LGTeacher: CustomStringConvertible {
    var description: String {
        get {
            return "LGTeacher: \(age)\(name)"
        }
    }
}

extension Dog: CustomStringConvertible {
    var description: String {
        get {
            return "Dog: \(name)\(type)"
        }
    }
}

func debug(subject: CustomStringConvertible){
    print(subject.description)
}

let t = LGTeacher()
let d = Dog()
debug(subject: t)
debug(subject: d)

看到这里我们就可以稍微的总结一下

  • Class本质上定义了一个对象是什么
  • Protocol本质上定义了一个对象有哪些行为

二.协议的基本语法

1.协议要求一个属性必须明确是getget和set

protocol MyProtocol {
    //必须是var声明的
    var age: Int { get set}
    //要求遵循协议的类/结构体必须要实现get方法
    var name: String { get }
}

//需要注意的是:并不是当前声明get的属性一定是计算属性

class LGTeacher: MyProtocol {
    var age: Int
    
    //此时的name并不是计算属性
    var name: String
    
    init(_ age: Int,_ name: String) {
        self.age = age
        self.name = name
    }
}

2.协议中的异变方法,表示在该方法可以改变其属性的实例,以及该实例的所有属性(用于枚举和结构体),在为类实现该方法的时候不需要写mutating关键字

protocol MyProtocol {
    mutating func test()
}

3.类在实现协议中的初始化器,必须使用required关键字修饰初始化器的实现(类的初始化器添加required修饰符来表明所有该类的子类如果要自定义初始化器就必须实现该初始化器)

关于required类与结构体中初始化器模块有详细讲解

protocol MyProtocol {
    init()
}

class LGPerson: MyProtocol {
    required init() {}
}

//添加final关键字后,就不需要required。因为该类不允许被继承,也就没有了子类实现该初始化器的说法了
final class LGStudent: MyProtocol {
    init() {}
}

4.类专用协议(通过添加AnyObject关键字到协议的继承列表,就可以限制只能被类类型采纳)

Mirror源码解析也讲解到了AnyObject

protocol MyProtocol: AnyObject {}

5.可选协议:不想强制让遵循协议的类类型实现

//定义一个可选协议一般有两种方式

/*
 方式1:使用@objc关键字,使用OC方式来使用optional声明去可选协议
 
 1.暴露给Objc运行时,依旧是函数表派发(如果是@objc + dynamic会改变为消息派发方式(objc_msgSend))
 2.值类型不能使用该protocol,只能被class使用
 */
@objc protocol MyProtocol {
    @objc optional func test()
}

class LGTeacher: MyProtocol {
    
//    func test() {
//        print("test")
//    }
    
}

/*
 方式2:使用extension来给出默认实现,来实现可选协议的功能
 
 一般我们在Swift中会使用这种方式来实现可选协议
 */

protocol OptionalProtocol{
    func method() //必须实现
    func method1() //可选
    func method2() //可选
}

extension OptionalProtocol {
    func method1() {}
    func method2() {}
}

这里总结一下@objc的使用

1.Selector中调用的方法需要在方法前声明@objc,目的是允许这个函数在运行时通过 Objective-C 的消息机制调用

let btn = UIButton()     
btn.addTarget(self, action: #selector(click), for: .touchUpInside)

@objc func click()  {      
    print("clicked")
}

2.协议的方法可选时,协议和方法前面都要加上@objc

@objc protocol MyProtocol {
    @objc optional func test()
}

3.用weak修饰协议时,协议前面要添加@objc

@objc protocol MyProtocol {
}

class LGTeacher {
    weak var delegate: MyProtocol?
}

4.类前面加上@objcMembers,那么它及其子类、扩展的方法都会隐式的加上@objc

@objcMembers
class LGTeacher {
}

如果此时不想在扩展里加@objc,可以使用@nonobjc修饰

@objcMembers
class LGTeacher {
}

@nonobjc extension LGTeacher {
    func test() {}
}

5.扩展前加上@objc,那么里面的方法都会隐式加上@objc

class LGTeacher {
}

@objc extension LGTeacher {
    func test() {}
}

6.函数前面加上@objc

class LGTeacher {
    
    //加上@objc将该函数暴露给Runtime,依旧是函数表派发
    @objc func test() {}
    
}

//加上@objc就可以使用Runtime相关API
let sel = #selector(LGTeacher.test)

let t = LGTeacher()

//当然这里只有让LGTeacher继承自NSObject才能执行这个sel

类与结构体(下)中函数派发方式模块中也有对@objc的讲解

二.协议原理探究

1.实例对象执行协议函数

protocol MyProtocol {
    func test()
}

class LGTeacher: MyProtocol {

    //v-table
    func test() {
        print("test")
    }
}

let t = LGTeacher()
t.test()

类与结构体(下)讲解到Swift类函数派发方式为函数表调度,那么这里的test()是函数表的调度吗?这里我们通过SIL来分析一下


// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1tAA9LGTeacherCvp          // id: %2
  %3 = global_addr @$s4main1tAA9LGTeacherCvp : $*LGTeacher // users: %8, %7
  %4 = metatype $@thick LGTeacher.Type            // user: %6
  // function_ref LGTeacher.__allocating_init()
  %5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
  store %6 to %3 : $*LGTeacher                    // id: %7
  %8 = load %3 : $*LGTeacher                      // users: %9, %10

  //这里的class_method为函数表调度方式
  %9 = class_method %8 : $LGTeacher, #LGTeacher.test : (LGTeacher) -> () -> (), $@convention(method) (@guaranteed LGTeacher) -> () // user: %10
 
  %10 = apply %9(%8) : $@convention(method) (@guaranteed LGTeacher) -> ()
  %11 = integer_literal $Builtin.Int32, 0         // user: %12
  %12 = struct $Int32 (%11 : $Builtin.Int32)      // user: %13
  return %12 : $Int32                             // id: %13
} // end sil function 'main'

//test函数声明在v-table当中
sil_vtable LGTeacher {
  #LGTeacher.test: (LGTeacher) -> () -> () : @$s4main9LGTeacherC4testyyF    // LGTeacher.test()
  #LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC    // LGTeacher.__allocating_init()
  #LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD  // LGTeacher.__deallocating_deinit
}

sil_witness_table hidden LGTeacher: MyProtocol module main {
  method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}

class_method函数表调度

因此从SIL分析得出,遵循该协议的类实现该协议方法,通过实例对象调用协议方法,还是函数表的调度

2.协议类型实例执行协议函数

此时我们把上面代码中的let t = LGTeacher()改为let t: MyProtocol = LGTeacher()

//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()

SIL代码

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1tAA10MyProtocol_pvp       // id: %2
  %3 = global_addr @$s4main1tAA10MyProtocol_pvp : $*MyProtocol // users: %9, %7
  %4 = metatype $@thick LGTeacher.Type            // user: %6
  // function_ref LGTeacher.__allocating_init()
  %5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %8
  %7 = init_existential_addr %3 : $*MyProtocol, $LGTeacher // user: %8
  store %6 to %7 : $*LGTeacher                    // id: %8
  %9 = open_existential_addr immutable_access %3 : $*MyProtocol to $*@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol // users: %11, %11, %10

  //此时的调度方式变成了witness_method
  %10 = witness_method $@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11
  
  %11 = apply %10<@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol>(%9) : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9
  %12 = integer_literal $Builtin.Int32, 0         // user: %13
  %13 = struct $Int32 (%12 : $Builtin.Int32)      // user: %14
  return %13 : $Int32                             // id: %14
} // end sil function 'main'

// test函数依旧声明在v-table当中
sil_vtable LGTeacher {
  #LGTeacher.test: (LGTeacher) -> () -> () : @$s4main9LGTeacherC4testyyF    // LGTeacher.test()
  #LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC    // LGTeacher.__allocating_init()
  #LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD  // LGTeacher.__deallocating_deinit
}

//此时多了一个witness_table。为每一个遵循协议的类记录实现协议相关的编码信息,也就是保存了协议函数的实现地址
sil_witness_table hidden LGTeacher: MyProtocol module main {
  method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}

witness_method会去查找这个类的witness-table(协议见证表)中找到方法的实现

关于此时witness_table中的s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW,也就是test()

// protocol witness for MyProtocol.test() in conformance LGTeacher
sil private [transparent] [thunk] @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed LGTeacher) -> () {
// %0                                             // user: %1
bb0(%0 : $*LGTeacher):
  %1 = load %0 : $*LGTeacher                      // users: %2, %3
  %2 = class_method %1 : $LGTeacher, #LGTeacher.test : (LGTeacher) -> () -> (), $@convention(method) (@guaranteed LGTeacher) -> () // user: %3
  %3 = apply %2(%1) : $@convention(method) (@guaranteed LGTeacher) -> ()
  %4 = tuple ()                                   // user: %5
  return %4 : $()                                 // id: %5
} // end sil function '$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW'

通过witness_table中的函数其实还是通过函数表调度方式调度LGTeacher.test

简单总结一下:witness_table做了一层桥接,通过witness_table为每个实现协议的类记录实现协议的编码信息,找到具体的函数实现,完成方法的调度。简单的理解,协议见证表记录录的就是实现的协议函数具体信息

比如说,当LGTeacher遵循了协议MyProtocol并实现了协议函数test(),那么通过协议类型调用函数test()时,就会创建一个witness_table,通过witness_table找到具体的函数实现,完成函数的调度。简单的来说就是利用witness_table做了一次桥接。

3.Arm64汇编分析witness_table

通过Arm64汇编代码分析,在t.test()打上一个断点

通过我们对汇编基础的认知,很明显t.test()对于的汇编代码为0x104f17a7c <+128>: blr x8。读取的内存x1加上0x8地址,存入x8
blr x8行打上断点,读取寄存器x8的值

//发现x8就是LGTeacher协议见证表中对test函数的
(lldb) register read x8
      x8 = 0x0000000104f17d04  projectTest`protocol witness for projectTest.MyProtocol.test() -> () in conformance projectTest.LGTeacher : projectTest.MyProtocol in projectTest at <compiler-generated>

进入blr x8

此时,这里的blr x8才是真正的test函数实现

断点打在blr x8,并读取寄存器x8

(lldb) register read x8
      x8 = 0x0000000104f17ab4  projectTest`projectTest.LGTeacher.test() -> () at main.swift:26

至此,通过汇编还原了witness_table的原理,也证实了通过SIL分析的逻辑。

问题1:两个类继承自同一协议会有一张还是两张witness_table?

答案肯定是两张,因为从witness_table为每个遵循协议的类记录实现协议函数的相关信息。

问题2:以下代码打印的值为什么?

1.extension添加默认实现

protocol MyProtocol {
    func test()
}

//为协议添加默认实现
extension MyProtocol {
    func test() {
        print("MyProtocol")
    }
}

class LGTeacher: MyProtocol {

    //v-table
    func test() {
        print("LGTeacher")
    }
}

//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()

当然是LGTeacher,因为添加的默认实现和通过witness_table找到函数的调用没有任何的关系。执行的逻辑是,通过witness_table找到在LGTeacher中对test的函数,完成函数的调用

2.如果注释掉func test()

protocol MyProtocol {
//    func test()
}

//为协议添加默认实现
extension MyProtocol {
    func test() {
        print("MyProtocol")
    }
}

class LGTeacher: MyProtocol {

    //v-table
    func test() {
        print("LGTeacher")
    }
}

//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()

答案是MyProtocol,因为对于t来说静态类型为MyProtocol,当执行到t.test()时,由于协议中没有了test函数,因此不会走协议见证表那套逻辑。而在extension MyProtocol中有test函数的实现,因此直接静态派发执行test函数

对应的的SIL

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  ...
  //直接执行MyProtocol.test()
  // function_ref MyProtocol.test()
  %10 = function_ref @$s4main10MyProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %11

  ...
} // end sil function 'main'

//此时的witness_table就为空了
sil_witness_table hidden LGTeacher: MyProtocol module main {
}

3.注释掉LGTeacher中的test函数实现

protocol MyProtocol {
    func test()
}

//为协议添加默认实现
extension MyProtocol {
    func test() {
        print("MyProtocol")
    }
}

class LGTeacher: MyProtocol {

    //v-table
//    func test() {
//        print("LGTeacher")
//    }
}

//t的静态类型:MyProtocol
//t的动态类型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()

其实这个也很好理解,此时的witness_table中实现协议的信息存的是extension中的默认实现,因此肯定打印为MyProtocol

对应的SIL

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  ...
  %10 = witness_method $@opened("30ED99C2-93C7-11EC-A66A-501FC65B9E38") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("30ED99C2-93C7-11EC-A66A-501FC65B9E38") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11
  ...
} // end sil function 'main'


// protocol witness for MyProtocol.test() in conformance LGTeacher
sil private [transparent] [thunk] @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : LGTeacher> (@in_guaranteed τ_0_0) -> () {
// %0                                             // user: %2
bb0(%0 : $*τ_0_0):
  
  //这里直接派发执行MyProtocol.test()
  // function_ref MyProtocol.test()
  %1 = function_ref @$s4main10MyProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %2
    
  %2 = apply %1<τ_0_0>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> ()
  %3 = tuple ()                                   // user: %4
  return %3 : $()                                 // id: %4
} // end sil function '$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW'


//此时vtable里没有了test函数
sil_vtable LGTeacher {
  #LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC    // LGTeacher.__allocating_init()
  #LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD    // LGTeacher.__deallocating_deinit
}

//协议见证表里里有test函数 ---> s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW
sil_witness_table hidden LGTeacher: MyProtocol module main {
  method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW    // protocol witness for MyProtocol.test() in conformance LGTeacher
}

4.探究witness_table存放位置

我们在研究vTable的时候,得出vTable是存在TargetClassDescriptor中的

接下来来探究witness_table

protocol Shape {
    var area: Double { get }
}

class Circle: Shape {
    var radious: Double
    
    init(_ radious: Double) {
        self.radious = radious
    }
    
    var area: Double {
        get {
            return radious * radious * Double.pi
        }
    }
}


//1.静态类型为Circle
var circle: Circle = Circle(10)

//这里很好理解,一个实例对象为16字节(metadata + refCount) + Double(8字节)
print(class_getInstanceSize(Circle.self)) // 24

//这个8也很好理解,说白了就是circle变量指针的大小。占据8字节大小
print(MemoryLayout.size(ofValue: circle)) // 8

/*
 (lldb) po withUnsafePointer(to: &circle) {print($0)}
 0x0000000100010480
 0 elements
 
 0x0000000100010480 ---> 变量circle的地址
 
 (lldb) x/8g 0x0000000100010480
 0x100010480: 0x0000000100724c40 0x0000000000000000
 0x100010490: 0x0000000000000000 0x0000000000000000
 0x1000104a0: 0x0000000000000000 0x0000000000000000
 0x1000104b0: 0x0000000000000000 0x0000000000000000
 
 (lldb) x/8g 0x0000000100724c40
 0x100724c40: 0x00000001000103c8 0x0000000000000003
 0x100724c50: 0x4024000000000000 0x0000000100724e40
 0x100724c60: 0x00000009a0080001 0x00007ff843a06f00
 0x100724c70: 0x0000000000000000 0x00007ff8422362d0
 
 0x00000001000103c8 ---> metadata
 0x0000000000000003 ---> refCount
 0x4024000000000000 ---> Double值10
 
 (lldb) cat address 0x0000000100724c40
 address:0x0000000100724c40, (String) $R1 = "0x100724c40 heap pointer, (0x20 bytes), zone: 0x7ff84226d000"
 
 浮点数的还原(lldb调试)
 (lldb) expr -f float -- 0x4024000000000000
 (Int) $R2 = 10
 
 得出结论:var circle: Circle = Circle(10),存的是堆空间的内存地址
 */




//2.静态类型为Shape
var shape: Shape = Circle(10)

//type(of:)获取的实际类型为Circle,因此大小肯定和Circle一样也是24
print(class_getInstanceSize(type(of: shape) as? AnyClass)) // 24

/*
 此时的数据类型占据的内存大小为40,和上面的8完全就不一样了。
 但是我们可以得出一个结论,静态类型不同,变量存储的内容是不一样的。
 猜想:肯定是多了witness_table的数据?
 */
print(MemoryLayout.size(ofValue: shape)) // 40


/*
 (lldb) po withUnsafePointer(to: &shape) {print($0)}
 0x0000000100010488
 0 elements

 (lldb) x/8g 0x0000000100010488
 0x100010488: 0x000000010b311280 0x0000000000000000
 0x100010498: 0x0000000000000000 0x00000001000103c8
 0x1000104a8: 0x000000010000c2e8 0x0000000000000000
 0x1000104b8: 0x0000000000000000 0x0000000000000000
 
 前5个8字节存放的就是shape的内容(40字节)
 
 分析第一个8字节 ---> 0x000000010b311280(实例对象的堆空间地址)
 (lldb) x/8g 0x000000010b311280
 0x10b311280: 0x00000001000103c8 0x0000000000000003
 0x10b311290: 0x4024000000000000 0x00007ff84223a498
 0x10b3112a0: 0x0000000000000001 0x00007ff84223a8e0
 0x10b3112b0: 0x0000000000000007 0x00007ff84223a240
 
 第二、三个8字节都为0
 
 分析第四个8字节 ---> 0x00000001000103c8(实例对象的metadata,动态类型的metadata)
 
 分析第五个8字节 ---> 0x000000010000c2e8(witness table)
 (lldb) cat address 0x000000010000c2e8
 address:0x000000010000c2e8, 1c8protocol witness table for swiftTest.Circle : swiftTest.Shape in swiftTest <+0> , ($s9swiftTest6CircleCAA5ShapeAAWP), External: NO swiftTest.__DATA_CONST.__const +1c8
 */

expr -f float -- 地址为浮点数的还原(lldb)

根据上述分析,大致得出的shape数据结构

struct LGProtocolBox {
    var heapObject: UnsafeRawPointer
    var unkown1: UnsafeRawPointer
    var unkown2: UnsafeRawPointer
    var metadata: UnsafeRawPointer
    var witness_table: UnsafeRawPointer
}

分析IR代码得出确定的类型,此时只留var shape: Shape = Circle(10)这行代码

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
    
  //获取Circle的metadata
  %3 = call swiftcc %swift.metadata_response @"$s4main6CircleCMa"(i64 0) #10
    
  //提取%3到%4,%4也是metadata
  %4 = extractvalue %swift.metadata_response %3, 0
    
  //$s4main6CircleCyACSdcfC ---> main.Circle.__allocating_init(Swift.Double) -> main.Circle
  //创建Circle实例变量
  %5 = call swiftcc %T4main6CircleC* @"$s4main6CircleCyACSdcfC"(double 1.000000e+01, %swift.type* swiftself %4)
  
  //$s4main5shapeAA5Shape_pvp ---> main.shape : main.Shape
  //%T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
  //%swift.type = type { i64 }
  //将metadata存入main.shape
  
  store %swift.type* %4, %swift.type** getelementptr inbounds
    (%T4main5ShapeP, %T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp", i32 0, i32 1), align 8

  //$s4main6CircleCAA5ShapeAAWP ---> protocol witness table for main.Circle : main.Shape in main
  //将数组的第一个元素也存进main.shape中第三个元素中
  store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6CircleCAA5ShapeAAWP", i32 0, i32 0),
    i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp", i32 0, i32 2), align 8
  
  //将创建Circle实例变量存入main.shape的第一个元素位置
  store %T4main6CircleC* %5, %T4main6CircleC**
    bitcast (%T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp" to %T4main6CircleC**), align 8
  ret i32 0
}

//s4main6CircleCAA5ShapeAAMc 存入第一个元素中
//s4main6CircleCAA5ShapeA2aDP4areaSdvgTW 存入第二个元素中(遵循协议的方法)

//$s4main6CircleCAA5ShapeAAMc ---> protocol conformance descriptor for main.Circle : main.Shape in main
//$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW ---> protocol witness for main.Shape.area.getter : Swift.Double in conformance main.Circle : main.Shape in main
@"$s4main6CircleCAA5ShapeAAWP" = hidden constant [2 x i8*]
[i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main6CircleCAA5ShapeAAMc" to i8*),
 i8* bitcast (double (%T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW" to i8*)], align 8

可以分析出TargetWitnessTable

struct TargetWitnessTable {
    var protocol_conformance_descriptor: UnsafeRawPointer
    var witnessMethod: UnsafeRawPointer
}

关于protocol_conformance_descriptor,我们可以去源码Metadata.h中找到

/// A witness table for a protocol.
///
/// With the exception of the initial protocol conformance descriptor,
/// the layout of a witness table is dependent on the protocol being
/// represented.
template <typename Runtime>
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;
  }
};

这里的Description就是我们要找的

进入TargetProtocolConformanceDescriptor找到成员

/// 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;

进入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;

  ...

}

根据源码补充TargetWitnessTable数据结构,并验证

struct LGProtocolBox {
    var heapObject: UnsafeRawPointer
    var unkown1: UnsafeRawPointer
    var unkown2: UnsafeRawPointer
    var metadata: UnsafeRawPointer
    var witness_table: UnsafePointer<TargetWitnessTable>
}

struct TargetWitnessTable {
    var protocol_conformance_descriptor: UnsafeMutablePointer<ProtocolConformanceDescriptor>
    var witnessMethod: UnsafeRawPointer
}

struct ProtocolConformanceDescriptor {
    var protocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
    var typeRef: UnsafeRawPointer
    var witnessTablePattern: UnsafeRawPointer
    var flags: UInt32
}

//TargetProtocolDescriptor 继承自 TargetContextDescriptor
struct TargetProtocolDescriptor {
    var flags: UInt32
    var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
    
    var name: TargetRelativeDirectPointer<CChar>
    var numRequirementsInSignature: UInt32
    var numRequirements: UInt32
    var associatedTypeNames: TargetRelativeDirectPointer<CChar>
}

var ptr = withUnsafePointer(to: &shape){UnsafeRawPointer($0).assumingMemoryBound(to: LGProtocolBox.self)}

var descPtr = ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset()

print(String(cString: descPtr.pointee.name.getMeasureRelativeOffset())) // Shape

print(ptr.pointee.witness_table.pointee.witnessMethod) // 0x0000000100004d40

//本质上执行area中get,还是通过Circle找到witness-table(PWT)再找到对应的的函数地址,开始调用

终端还原一下0x0000000100004d40

❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004d40
0000000100004d40 t _$s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW
❯ xcrun swift-demangle s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW
$s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW ---> protocol witness for swiftTest.Shape.area.getter : Swift.Double in conformance swiftTest.Circle : swiftTest.Shape in swiftTest

0x0000000100004d40就是Circle中遵循协议并实现area中get的函数地址

总结:

  • 每个遵守了协议的类,都会有自己的PWT(Protocol Witness Table),遵守的协议函数越多,PWT中存储的函数地址就越多
  • PWT的本质就是一个指针数组,第一个元素存储ProtocolConformanceDescriptor,其后面存储的是函数地址
  • PWT的数量与协议数量一致

三.Existential Container

我们之前总结的LGProtocolBox,就称为Existential Container

struct LGProtocolBox {
    var valueBuffer1: UnsafeRawPointer
    var valueBuffer2: UnsafeRawPointer
    var valueBuffer3: UnsafeRawPointer
    var metadata: UnsafeRawPointer
    var witness_table: UnsafePointer<TargetWitnessTable>
}

为什么要有Existential Container?

//静态类型为Circle
var circle: Circle = Circle(10)
静态类型为Shape
var shape: Shape = Circle(10)

如果静态类型为具体的类型,那么编译器在编译的时候知道分配多大的内存空间来去存储变量。
但是静态类型为协议类型,那么编译器在编译的时候不知道分配多大的内存空间去存储。有可能是引用类型,有可能是值类型。因此使用Existential Container作为中间层存储(40字节)。

Existential Container是协议类型在编译过程中不确定的编译器技术。

Existential Container为什么要存储metadata?

对于我们Existential Container也需要记录它的metadata,需要记录它的真实类型信息。当我们调用属性或方法的时候才能找到metadata

1.小容量数据

小容量数据大小不超过24字节的数据,直接存放在Value Buffer
Value Buffer指的是Existential Container前3个8字节

例如:Circle只有一个变量

protocol Shape {
    var area: Double { get }
}

struct Circle: Shape {
    var radious: Double
    
    init(_ radious: Double) {
        self.radious = radious
    }
    
    var area: Double {
        get {
            return radious * radious * Double.pi
        }
    }
}

var circle: Shape = Circle(10)

/*
 (lldb) po withUnsafePointer(to: &circle) {print($0)}
 0x00000001000102b8
 0 elements

 (lldb) x/8g 0x00000001000102b8
 0x1000102b8: 0x4024000000000000 0x0000000000000000
 0x1000102c8: 0x0000000000000000 0x000000010000c308
 0x1000102d8: 0x000000010000c2f0 0x0000000000000000
 0x1000102e8: 0x0000000000000000 0x0000000000000000
 
 通过这里,可以看出第一个Value Buffer直接存放了10
 
 (lldb) expr -f float -- 0x4024000000000000
 (Int) $R1 = 10
 (lldb)
 */

例如:Circle有3个变量(占满24字节)

protocol Shape {
    var area: Double { get }
}

struct Circle: Shape {
    var radious: Double
    var radious1 = 10
    var radious2 = 10
    
    init(_ radious: Double) {
        self.radious = radious
    }
    
    var area: Double {
        get {
            return radious * radious * Double.pi
        }
    }
}

var circle: Shape = Circle(10)

/*
 (lldb) po withUnsafePointer(to: &circle) {print($0)}
 0x00000001000102b8
 0 elements

 (lldb) x/8g 0x00000001000102b8
 0x1000102b8: 0x4024000000000000 0x000000000000000a
 0x1000102c8: 0x000000000000000a 0x000000010000c360
 0x1000102d8: 0x000000010000c2f0 0x0000000000000000
 0x1000102e8: 0x0000000000000000 0x0000000000000000
 (lldb)
 
 第一个Value Buffer直接存放了10.0
 第二个Value Buffer直接存放了10
 第三个Value Buffer直接存放了10
 
 刚好使用完Value Buffer空间
 */

2.大容量数据

大容量数据大小超过24字节的数据,通过堆区分配,存储堆空间地址

例如:我们之前观察的shape,存放的是Heap Pointer

例如:Circle有4个变量(32字节)

protocol Shape {
    var area: Double { get }
}

struct Circle: Shape {
    var radious: Double
    var radious1 = 10
    var radious2 = 10
    var radious3 = 10
    
    init(_ radious: Double) {
        self.radious = radious
    }
    
    var area: Double {
        get {
            return radious * radious * Double.pi
        }
    }
}

var circle: Shape = Circle(10)


/*
 (lldb) po withUnsafePointer(to: &circle) {print($0)}
 0x00000001000102c8
 0 elements

 (lldb) x/8g 0x00000001000102c8
 0x1000102c8: 0x0000000101522120 0x0000000000000000
 0x1000102d8: 0x0000000000000000 0x000000010000c388
 0x1000102e8: 0x000000010000c318 0x0000000000000000
 0x1000102f8: 0x0000000000000000 0x0000000000000000
 
 此时的Value Buffer1存放了开辟的堆空间地址
 
 
 (lldb) x/8g 0x0000000101522120
 0x101522120: 0x000000010000c300 0x0000000000000003
 0x101522130: 0x4024000000000000 0x000000000000000a
 0x101522140: 0x000000000000000a 0x000000000000000a
 0x101522150: 0x0000000000000000 0x0000000000000000
 
 0x0000000101522120 ---> 开辟的堆空间内存地址
 除去metadata及refCount后,依次存储4条数据
 
 
 (lldb) x/8g 0x000000010000c300
 0x10000c300: 0x0000000000000400 0x0000000000000010
 0x10000c310: 0x000000010000b944 0x000000010000a640
 0x10000c320: 0x0000000100004de0 0x0000000100004e80
 0x10000c330: 0x00000001000044e0 0x0000000100004eb0
 
 这里的metadata指的是开辟的内存空间的metadata,此时类型为HeapLocalVariable(0x400)。
 
 如果Circle是引用类型,堆空间的metadata和Existential Container中的metadata是一致的。
 此时算小容量存储,将Heap Pointer存到Value Buffer1上。因此外部的metadata和内部的是一致的

 
 (lldb) x/8g 0x000000010000c388
 0x10000c388: 0x0000000000000200 0x000000010000a690
 0x10000c398: 0x0000000800000000 0x0000001800000010
 0x10000c3a8: 0x0000000100005d40 0x00000001000044e0
 0x10000c3b8: 0x0000000100004730 0x0000000100004730
 
 这里的metadata指的是遵循协议的metadata,也就是Circle的metadata。类型为Struct(0x200)
 
 (lldb)
 */

总结:

  • Existential Container是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协议类型,因为这些类型的内存大小不一致,所以通过当前的Existential Container统一管理
  • 对于小容量的数据,直接存储在Value Buffer
  • 对于大容量的数据,通过堆区分配,存储堆空间的地址

四.写时复制

当存放大容量的数据时,值类型数据会放入堆空间内存,如果此时去修改它,会发生什么变化?

protocol Shape {
    var radious: Int { get set }
    var radious1: Int { get set }
    var radious2: Int { get set }
    var radious3: Int { get set }
}

struct Circle: Shape {
    var radious = 10
    var radious1 = 20
    var radious2 = 30
    var radious3 = 40
    
}

var circle: Shape = Circle()
var circle1 = circle

/*
 修改radious前的LLDB调试信息
 
 (lldb) po withUnsafePointer(to: &circle) {print($0)}
 0x00000001000102e8
 0 elements

 (lldb) x/8g 0x00000001000102e8
 0x1000102e8: 0x000000010b2c1b80 0x0000000000000000
 0x1000102f8: 0x0000000000000000 0x000000010000c3e0
 0x100010308: 0x000000010000c318 0x000000010b2c1b80
 0x100010318: 0x0000000000000000 0x0000000000000000
 (lldb) po withUnsafePointer(to: &circle1) {print($0)}
 0x0000000100010310
 0 elements

 (lldb) x/8g 0x0000000100010310
 0x100010310: 0x000000010b2c1b80 0x0000000000000000
 0x100010320: 0x0000000000000000 0x000000010000c3e0
 0x100010330: 0x000000010000c318 0x0000000000000000
 0x100010340: 0x0000000000000000 0x0000000000000000
 (lldb)
 
 此时发现circle和circle1的ValueBuffer中存储是同一个堆空间地址0x000000010b2c1b80
 */


circle.radious = 0

/*
 修改radious后的LLDB调试信息
 
 (lldb) x/8g 0x00000001000102e8
 0x1000102e8: 0x0000000100706400 0x0000000000000000
 0x1000102f8: 0x0000000000000000 0x000000010000c3e0
 0x100010308: 0x000000010000c318 0x000000010b2c1b80
 0x100010318: 0x0000000000000000 0x0000000000000000
 
 (lldb) x/8g 0x0000000100706400
 0x100706400: 0x00007ff84223c1f8 0x0000000000000003
 0x100706410: 0x0000000000000000 0x0000000000000014
 0x100706420: 0x000000000000001e 0x0000000000000028
 0x100706430: 0x0000000000000003 0x00007ff84223a3d8
 (lldb)
 
 
 (lldb) x/8g 0x0000000100010310
 0x100010310: 0x000000010b2c1b80 0x0000000000000000
 0x100010320: 0x0000000000000000 0x000000010000c3e0
 0x100010330: 0x000000010000c318 0x0000000000000000
 0x100010340: 0x0000000000000000 0x0000000000000000
 (lldb)
 
 此时发现,当修改了circle的radious后,circle中的堆空间地址发生了变化
 相当于复制了一份到新的堆空间,然后修改了radious的值
 */

对于修改radious后,复制了一块新的堆区空间来存储的现象,就称为写时复制

修改radious的时候,会去判断堆空间的引用计数是是否为1,如果大于1就会进行写时复制,把当前堆区数据复制一份然后再修改radious的值。

原理:对于协议类型,当值类型的值超过了Value Buffer,会开辟了堆空间存储。系统在修改值的过程中,先去检测引用计数,如果引用计数大于1就会开辟内存空间,否则的话就不开辟。因为数据本身还是值类型,修改值的同时不能影响其它数据,不能表现为引用类型,也就是写时复制的原因。

好处:针对值类型,提高内存指针的利用率,降低堆区的内存的消耗,从而提高性能。

问题1:如果将Circle改为class,会是什么情况?还会有写时复制吗?

首先要了解一个问题,为什么要写时复制。当Circle是值类型时,此时circle1修改了值,能影响circle的值吗?答案是不能的。因此,在协议类型修改circle1的值时,为了不影响circle的值,所以有了写时复制,来保证原有值不会被修改。

那么我们再来分析如果circle1circle引用类型,引用类型修改会影响其它一条数据,那么此时需要写时复制吗?肯定也不是需要的。所以对于引用类型来说,就没有写时复制的概念。

五.还原TargetProtocolMetadata

在源码中并未找到关于TargetProtocolMetadata相关信息,这里由Mirror源码解析中介绍到的TargetProtocolMetadata拓展研究得出的。

如果谁知道关于TargetProtocolMetadata文档,希望告诉一下,谢谢。

//指针数组
//第一个元素ProtocolConformanceDescriptor,从二个开始存放的才是函数指针
struct TargetWitnessTable {
    var protocol_conformance_descriptor: UnsafeMutablePointer<ProtocolConformanceDescriptor>
    
    var witnessMethod: UnsafeRawPointer
    
    //如果还有协议函数,跟在后面依次排列
}

//记录的是遵守协议的一些信息
struct ProtocolConformanceDescriptor {
    var protocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
    
    // Some description of the type that conforms to the protocol.
    var typeRef: UnsafeRawPointer
    
    // The witness table pattern, which may also serve as the witness table.
    var witnessTablePattern: UnsafeRawPointer
    
    // Various flags, including the kind of conformance.
    //标志位
    var flags: UInt32
}

//TargetProtocolDescriptor 继承自 TargetContextDescriptor
struct TargetProtocolDescriptor {
    //TargetContextDescriptor中的数据
    var flags: UInt32
    var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
    
    //协议名称
    var name: TargetRelativeDirectPointer<CChar>
    
    // The number of generic requirements in the requirement signature of the
    // protocol.
    var numRequirementsInSignature: UInt32
    
    //需要遵循协议的数量
    var numRequirements: UInt32
    
    // Associated type names, as a space-separated list in the same order
    // as the requirements.
    //关联类型名称
    var associatedTypeNames: TargetRelativeDirectPointer<CChar>
}

struct TargetProtocolMetadata {
    var type: Any.Type
    var witness_table: UnsafePointer<TargetWitnessTable>
}

//还原TargetProtocolMetadata

protocol MyProtocol {
    func test()
}

struct LGTeacher: MyProtocol {
    func test() {}
}

var type: MyProtocol.Type = LGTeacher.self

let protocolMetadata = unsafeBitCast(type, to: TargetProtocolMetadata.self)

print(protocolMetadata.type) //LGTeacher,遵循协议的类型

print(String(cString: protocolMetadata.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol

print(protocolMetadata.witness_table.pointee.witnessMethod) // 函数地址0x0000000100004cb0

/*
 使用终端还原mach-o中的0x0000000100004cb0
 
 ❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004cb0
 0000000100004cb0 t _$s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW
 
 ❯ xcrun swift-demangle s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW
 $s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW ---> protocol witness for swiftTest.MyProtocol.test() -> () in conformance swiftTest.LGTeacher : swiftTest.MyProtocol in swiftTest
 
 得出这个函数地址就是遵循MyProtocol协议的函数地址,也就是LGTeacher中的test函数地址
 */

六.问题探究

1.如果有一个类遵循了2个协议,那么Existential Container里的witness table里的数据是怎么存放的?

我们知道witness table是一个指针数组,第一条数据存放的是协议信息,后续数据存放的是遵循协议的函数地址

protocol Myprotocol {
    func test()
}

protocol MyProtocol1 {
    func test1()
}

typealias CustomProtocol = Myprotocol & MyProtocol1

class LGTeacher: CustomProtocol {
    func test(){
        print("test")
    }
    func test1(){
        print("test1")
    }
}

var t: CustomProtocol = LGTeacher()

IR代码分析

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  %3 = call swiftcc %swift.metadata_response @"$s4main9LGTeacherCMa"(i64 0) #4
  %4 = extractvalue %swift.metadata_response %3, 0
  %5 = call swiftcc %T4main9LGTeacherC* @"$s4main9LGTeacherCACycfC"(%swift.type* swiftself %4)

  //$s4main1tAA11MyProtocol1_AA10Myprotocolpvp ---> main.t : main.MyProtocol1 & main.Myprotocol
  //存metadata到index1
  store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 1), align 8
  
  //$s4main9LGTeacherCAA11MyProtocol1AAWP ---> protocol witness table for main.LGTeacher : main.MyProtocol1 in main
  //存Protocol1的协议见证表到index2
  store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9LGTeacherCAA11MyProtocol1AAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 2), align 8
  
  //$s4main9LGTeacherCAA10MyprotocolAAWP ---> protocol witness table for main.LGTeacher : main.Myprotocol in main
  //存Protocol1的协议见证表到inde3
  store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9LGTeacherCAA10MyprotocolAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 3), align 8
   
  //将LGTeacher变量存入第一个元素
  store %T4main9LGTeacherC* %5, %T4main9LGTeacherC** bitcast (%T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp" to %T4main9LGTeacherC**), align 8
  ret i32 0
}

通过IR代码可以得出Existential Container数据结构

struct LGProtocolBox {
    //此时这里存放的是LGTeacher实例的Heap Pointer
    var valueBuffer1: UnsafeRawPointer
    //0x0,空闲
    var valueBuffer2: UnsafeRawPointer
    //0x0,空闲
    var valueBuffer3: UnsafeRawPointer
    //LGTeacher的metadata
    var metadata: UnsafeRawPointer

    //MyProtocol1的witness_table
    var witness_table: UnsafePointer<TargetWitnessTable>
    //MyProtocol的witness_table
    var witness_table1: UnsafePointer<TargetWitnessTable>
}

注意哦,此时的Existential Container大小就不再是40了,而是48

通过代码验证,当然这里也可以使用LLDB命令验证

protocol Myprotocol {
    func test()
}

protocol MyProtocol1 {
    func test1()
}

typealias CustomProtocol = Myprotocol & MyProtocol1

class LGTeacher: CustomProtocol {
    func test(){}
    func test1(){}
}

var t: CustomProtocol = LGTeacher()


//存在容器大小变为了48,每多一个协议,大小多8字节来存放witness_table
print(MemoryLayout.size(ofValue: t)) //48

let ptr = withUnsafePointer(to: &t) {
    UnsafeRawPointer($0).assumingMemoryBound(to: LGProtocolBox.self)
}

print(String(cString: ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol1
print(String(cString: ptr.pointee.witness_table1.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // Myprotocol

print(ptr.pointee.witness_table.pointee.witnessMethod) //0x0000000100004bb0
print(ptr.pointee.witness_table1.pointee.witnessMethod) //0x0000000100004b90

/*
 ❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004bb0
 0000000100004bb0 t _$s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW
 ❯ xcrun swift-demangle s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW
 $s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW ---> protocol witness for swiftTest.MyProtocol1.test1() -> () in conformance swiftTest.LGTeacher : swiftTest.MyProtocol1 in swiftTest
 
 ❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004b90
 0000000100004b90 t _$s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW
 ❯ xcrun swift-demangle s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW
 $s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW ---> protocol witness for swiftTest.Myprotocol.test() -> () in conformance swiftTest.LGTeacher : swiftTest.Myprotocol in swiftTest
 */

总结:
Existential Container大小并不是固定是40字节的,当多协议类型时,Existential Container会扩容,每多一个协议会扩容8字节来存入PWT

2.如果有一个类遵循了2个协议,还原一下协议的metadata

其实逻辑与Existential Container一致,多了一个协议多了一个witness_table,那么在Metadata中也会多一个witness_table

此时TargetProtocolMetadata数据结构

struct TargetProtocolMetadata {
    var type: Any.Type
    var witness_table: UnsafePointer<TargetWitnessTable>
    var witness_table1: UnsafePointer<TargetWitnessTable>
}
protocol Myprotocol {
    func test()
}

protocol MyProtocol1 {
    func test1()
}

typealias CustomProtocol = Myprotocol & MyProtocol1

class LGTeacher: CustomProtocol {
    func test(){}
    func test1(){}
}

var type: CustomProtocol.Type = LGTeacher.self

let ptr = unsafeBitCast(type, to: TargetProtocolMetadata.self)

print(ptr.type) //LGTeacher,遵循协议的类型

print(String(cString: ptr.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol1

//函数地址就不一一还原了,这里肯定就是LGTeacher实现协议MyProtocol1中test函数的地址
print(ptr.witness_table.pointee.witnessMethod) // 函数地址0x0000000100004af0

print(String(cString: ptr.witness_table1.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // Myprotocol

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

推荐阅读更多精彩内容