Swift底层原理-协议

Swift底层原理-协议

协议的基本用法

协议的定义

  • 如若想使用协议,那么我们需要使用protocol关键字来申明协议。
  • 协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)。
protocol BaseProtocol {
    var x: Int {get set}
    var y: Double {get}
}

class TestClass: BaseProtocol {
    var x: Int = 0
    
    var y: Double = 0.0
}

协议中的属性

  • 协议中定义属性时必须用var关键字。
  • 在定义属性时,我们必须指定属性至少是可读的,即我们需要给属性添加 { get } 属性,也可以是{get set}; 同时我们要注意 这个 get 并不一定指定属性就是计算属性。
protocol BaseProtocol {
    var x: Int {get set}
    var y: Double {get}
}

class TestClass: BaseProtocol {
    // 可读写计算属性
    var x: Int {
        get {
            return 10
        }
        set {
            self.x = newValue
        }
    }
    
    // 可读计算属性
    var y: Double {
        get {
            return 20
        }
    }
}

class TestClass: BaseProtocol {
    // 变量存储属性,可读写
    var x: Int = 0
    
    // 变量存储属性,可读写
    var y: Double = 0.0
}
  • 若协议要求一个属性为可读和可写的,那么该属性要求不能用常量存储属性(let)或只读计算属性来满足。
  • 若协议只要求属性为可读的,那么任何种类的属性都能满足这个要求,而且如果你的代码需要的话,该属性也可以是可写的。

协议的方法

  • 写方式与正常实例和类方法的方式完全相同,但是不需要大括号和方法的主体。但在协议的定义中,方法参数不能定义默认值
protocol BaseProtocol {
    func test()
}

类型方法

  • 当协议中定义类型方法时,你总要在其之前添加static关键字。即使在类实现时,类型方法要求使用classstatic作为关键字前缀
protocol BaseProtocol {
    static func test()
}

class TestClass: BaseProtocol {
    static func test() {
        print("test")
    }
}

异变方法

  • mutating只有将协议中的实例方法标记为mutating,才允许结构体、枚举的具体实现修改自身内存。
  • 如果你在协议中标记实例方法需求为mutating,在为类实现该方法的时候不需要写mutating关键字。 mutating关键字只在结构体和枚举类型中需要书写。
protocol BaseProtocol {
    mutating func test()
}

class TestClass: BaseProtocol {
    func test() {
        print("test")
    }
}

struct TestStruct: BaseProtocol {
    var x = 10
    mutating func test() {
        x = 20
    }
}

初始化方法

  • 协议中定义的初始化方法,在遵循这个协议时,我们需要在实现这个初始化方法时 在init前加上required关键字,否则编译器会报错的(类的初始化 器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器)。
protocol BaseProtocol {
    init()
}

class TestClass: BaseProtocol {
    required init() {
        
    }
}
  • 由于final的类不会有子类,如果协议初始化器实现的类使用了final标记,你就不需要使用required来修饰了。
protocol BaseProtocol {
    init()
}

final class TestClass: BaseProtocol {
    init() {
        
    }
}

可选协议

  • 你可以给协议定义可选要求,这些要求不需要强制遵循协议的类型实现
  • 可以用 optional 作为前缀放在协议的定义,但需要注意的是我们还需要增加 @objc 关键字
@objc protocol BaseProtocol {
    @objc optional func test()
}
  • 也可用extension实现
protocol BaseProtocol {
    func test()
}

extension BaseProtocol {
    func test() {
        
    }
}

协议的继承和组合

  • 协议可以继承一个或者多个其他协议并且可以在它继承的基础之上添加更多要求
protocol BaseProtocol {
    func test()
}

protocol BaseProtocol1 {
    
}

class TestClass: BaseProtocol, BaseProtocol1 {
}
  • 在协议后面写上:AnyObject代表只有类能遵守这个协议,在协议后面写上:class也代表只有类能遵守这个协议。
protocol BaseProtocol: AnyObject {}
protocol BaseProtocol: class {}

协议的底层原理

协议的方法调度

  • 在方法这篇文章中,我们知道类的方法的调度是通过虚函数表(VTable)查找到对应的函数进行调用的,而结构体的方法直接就是拿到函数的地址进行调用。
  • 那么协议中声明的方法呢,如果类或者结构体遵守这个协议,然后实现协议方法,它是如何去查找函数的地址进行调用的呢。

类实现协议

静态类型为类类型
protocol BaseProtocol {
    func test(_ number: Int)
}

class TestClass: BaseProtocol {
    var x: Int?
    func test(_ number: Int) {
        x = number
    }
}

var test: TestClass = TestClass()
test.test(10)

  • 然后把当前的main.swift文件编译成main.sil文件。编译完成后找到main函数,查看test(:)方法的调用
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main4testAA9TestClassCvp       // id: %2
  %3 = global_addr @$s4main4testAA9TestClassCvp : $*TestClass // users: %8, %7
  %4 = metatype $@thick TestClass.Type            // user: %6
  // function_ref TestClass.__allocating_init()
  %5 = function_ref @$s4main9TestClassCACycfC : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %7
  store %6 to [init] %3 : $*TestClass             // id: %7
  %8 = begin_access [read] [dynamic] %3 : $*TestClass // users: %10, %9
  %9 = load [copy] %8 : $*TestClass               // users: %17, %16, %15
  end_access %8 : $*TestClass                     // id: %10
  %11 = integer_literal $Builtin.IntLiteral, 10   // 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 : $TestClass, #TestClass.test : (TestClass) -> (Int) -> (), $@convention(method) (Int, @guaranteed TestClass) -> () // user: %16
  %16 = apply %15(%14, %9) : $@convention(method) (Int, @guaranteed TestClass) -> ()
  destroy_value %9 : $TestClass                   // 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类型,class_method类型的方法是通过VTable查找.
  • 我们移动到该文件底部
sil_vtable TestClass {
  #TestClass.x!getter: (TestClass) -> () -> Int? : @$s4main9TestClassC1xSiSgvg  // TestClass.x.getter
  #TestClass.x!setter: (TestClass) -> (Int?) -> () : @$s4main9TestClassC1xSiSgvs    // TestClass.x.setter
  #TestClass.x!modify: (TestClass) -> () -> () : @$s4main9TestClassC1xSiSgvM    // TestClass.x.modify
  #TestClass.test: (TestClass) -> (Int) -> () : @$s4main9TestClassC4testyySiF   // TestClass.test(_:)
  #TestClass.init!allocator: (TestClass.Type) -> () -> TestClass : @$s4main9TestClassCACycfC    // TestClass.__allocating_init()
  #TestClass.deinit!deallocator: @$s4main9TestClassCfD  // TestClass.__deallocating_deinit
}
  • 可以发现test方法确实存在VTable中。所以当静态类型是TestClass,通过VTable来调用。
静态类型为协议类型
  • 我们把test修改成协议类型
var test: BaseProtocol = TestClass()
  • 我们查看编译后main.sil文件,找到main函数
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main4testAA12BaseProtocol_pvp  // id: %2
  %3 = global_addr @$s4main4testAA12BaseProtocol_pvp : $*BaseProtocol // users: %9, %7
  %4 = metatype $@thick TestClass.Type            // user: %6
  // function_ref TestClass.__allocating_init()
  %5 = function_ref @$s4main9TestClassCACycfC : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %8
  %7 = init_existential_addr %3 : $*BaseProtocol, $TestClass // user: %8
  store %6 to [init] %7 : $*TestClass             // id: %8
  %9 = begin_access [read] [dynamic] %3 : $*BaseProtocol // users: %12, %11
  %10 = alloc_stack $BaseProtocol                 // users: %21, %20, %13, %11
  copy_addr %9 to [initialization] %10 : $*BaseProtocol // id: %11
  end_access %9 : $*BaseProtocol                  // id: %12
  %13 = open_existential_addr immutable_access %10 : $*BaseProtocol to $*@opened("39CCE43C-59EA-11ED-A979-86E79974171B") BaseProtocol // users: %19, %19, %18
  %14 = integer_literal $Builtin.IntLiteral, 10   // 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("39CCE43C-59EA-11ED-A979-86E79974171B") BaseProtocol, #BaseProtocol.test : <Self where Self : BaseProtocol> (Self) -> (Int) -> (), %13 : $*@opened("39CCE43C-59EA-11ED-A979-86E79974171B") BaseProtocol : $@convention(witness_method: BaseProtocol) <τ_0_0 where τ_0_0 : BaseProtocol> (Int, @in_guaranteed τ_0_0) -> () // type-defs: %13; user: %19
  %19 = apply %18<@opened("39CCE43C-59EA-11ED-A979-86E79974171B") BaseProtocol>(%17, %13) : $@convention(witness_method: BaseProtocol) <τ_0_0 where τ_0_0 : BaseProtocol> (Int, @in_guaranteed τ_0_0) -> () // type-defs: %13
  destroy_addr %10 : $*BaseProtocol               // id: %20
  dealloc_stack %10 : $*BaseProtocol              // 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类型,我们移动到该文件最后,发现了sil_witness_table
sil_witness_table hidden TestClass: BaseProtocol module main {
  method #BaseProtocol.test: <Self where Self : BaseProtocol> (Self) -> (Int) -> () : @$s4main9TestClassCAA12BaseProtocolA2aDP4testyySiFTW  // protocol witness for BaseProtocol.test(_:) in conformance TestClass
}
  • 在该表中,也定一个test方法,我们去查找该方法的定义
// protocol witness for BaseProtocol.test(_:) in conformance TestClass
sil private [transparent] [thunk] [ossa] @$s4main9TestClassCAA12BaseProtocolA2aDP4testyySiFTW : $@convention(witness_method: BaseProtocol) (Int, @in_guaranteed TestClass) -> () {
// %0                                             // user: %4
// %1                                             // user: %2
bb0(%0 : $Int, %1 : $*TestClass):
  %2 = load_borrow %1 : $*TestClass               // users: %6, %4, %3
  %3 = class_method %2 : $TestClass, #TestClass.test : (TestClass) -> (Int) -> (), $@convention(method) (Int, @guaranteed TestClass) -> () // user: %4
  %4 = apply %3(%0, %2) : $@convention(method) (Int, @guaranteed TestClass) -> ()
  %5 = tuple ()                                   // user: %7
  end_borrow %2 : $TestClass                      // id: %6
  return %5 : $()                                 // id: %7
} // end sil function '$s4main9TestClassCAA12BaseProtocolA2aDP4testyySiFTW'
  • 在定义中,最终还是会去查找遵守它的类中的VTable进行方法的调度。
  • 总结
    • 如果实例对象的静态类型就是确定的类型,那么这个协议方法通过VTalbel进行调度。
    • 如果实例对象的静态类型是协议类型,那么这个协议方法通过witness_table中对应的协议方法,然后通过协议方法去查找遵守协议的类的VTable进行调度。

结构体实现协议

  • 在上面,我们研究了遵循协议类的方法调用,下面我们研究一下结构体遵循协议后是如何调用
静态类型为结构体类型
protocol BaseProtocol {
    func test()
}

struct TestStruct: BaseProtocol {
    func test() {
    }
}

var test: TestStruct = TestStruct()
test.test()
  • 把当前的main.swift文件编译成main.sil文件。编译完成后找到main函数,查看test()方法的调用
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main4testAA10TestStructVvp     // id: %2
  %3 = global_addr @$s4main4testAA10TestStructVvp : $*TestStruct // users: %8, %7
  %4 = metatype $@thin TestStruct.Type            // user: %6
  // function_ref TestStruct.init()
  %5 = function_ref @$s4main10TestStructVACycfC : $@convention(method) (@thin TestStruct.Type) -> TestStruct // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thin TestStruct.Type) -> TestStruct // user: %7
  store %6 to [trivial] %3 : $*TestStruct         // id: %7
  %8 = begin_access [read] [dynamic] %3 : $*TestStruct // users: %10, %9
  %9 = load [trivial] %8 : $*TestStruct           // user: %12
  end_access %8 : $*TestStruct                    // id: %10
  // function_ref TestStruct.test()
  %11 = function_ref @$s4main10TestStructV4testyyF : $@convention(method) (TestStruct) -> () // user: %12
  %12 = apply %11(%9) : $@convention(method) (TestStruct) -> ()
  %13 = integer_literal $Builtin.Int32, 0         // user: %14
  %14 = struct $Int32 (%13 : $Builtin.Int32)      // user: %15
  return %14 : $Int32                             // id: %15
} // end sil function 'main'
  • 通过%11可以看到,结构体调用协议方法的方式直接就是函数地址调用。
静态类型为协议类型
  • 我们把test修改成协议类型
var test: BaseProtocol = TestStruct()
  • 查看编译后文件的main函数
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main4testAA12BaseProtocol_pvp  // id: %2
  %3 = global_addr @$s4main4testAA12BaseProtocol_pvp : $*BaseProtocol // users: %9, %7
  %4 = metatype $@thin TestStruct.Type            // user: %6
  // function_ref TestStruct.init()
  %5 = function_ref @$s4main10TestStructVACycfC : $@convention(method) (@thin TestStruct.Type) -> TestStruct // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thin TestStruct.Type) -> TestStruct // user: %8
  %7 = init_existential_addr %3 : $*BaseProtocol, $TestStruct // user: %8
  store %6 to [trivial] %7 : $*TestStruct         // id: %8
  %9 = begin_access [read] [dynamic] %3 : $*BaseProtocol // users: %12, %11
  %10 = alloc_stack $BaseProtocol                 // users: %17, %16, %13, %11
  copy_addr %9 to [initialization] %10 : $*BaseProtocol // id: %11
  end_access %9 : $*BaseProtocol                  // id: %12
  %13 = open_existential_addr immutable_access %10 : $*BaseProtocol to $*@opened("7CCB7D78-59EC-11ED-B59A-86E79974171B") BaseProtocol // users: %15, %15, %14
  %14 = witness_method $@opened("7CCB7D78-59EC-11ED-B59A-86E79974171B") BaseProtocol, #BaseProtocol.test : <Self where Self : BaseProtocol> (Self) -> () -> (), %13 : $*@opened("7CCB7D78-59EC-11ED-B59A-86E79974171B") BaseProtocol : $@convention(witness_method: BaseProtocol) <τ_0_0 where τ_0_0 : BaseProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %13; user: %15
  %15 = apply %14<@opened("7CCB7D78-59EC-11ED-B59A-86E79974171B") BaseProtocol>(%13) : $@convention(witness_method: BaseProtocol) <τ_0_0 where τ_0_0 : BaseProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %13
  destroy_addr %10 : $*BaseProtocol               // id: %16
  dealloc_stack %10 : $*BaseProtocol              // 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'
  • 通过%14这一行,发现它的类型变成了witness_method。我们查看该方法的实现
// protocol witness for BaseProtocol.test() in conformance TestStruct
sil private [transparent] [thunk] [ossa] @$s4main10TestStructVAA12BaseProtocolA2aDP4testyyFTW : $@convention(witness_method: BaseProtocol) (@in_guaranteed TestStruct) -> () {
// %0                                             // user: %1
bb0(%0 : $*TestStruct):
  %1 = load [trivial] %0 : $*TestStruct           // user: %3
  // function_ref TestStruct.test()
  %2 = function_ref @$s4main10TestStructV4testyyF : $@convention(method) (TestStruct) -> () // user: %3
  %3 = apply %2(%1) : $@convention(method) (TestStruct) -> ()
  %4 = tuple ()                                   // user: %5
  return %4 : $()                                 // id: %5
} // end sil function '$s4main10TestStructVAA12BaseProtocolA2aDP4testyyFTW'
  • 通过%2可以看到,它最终还是找到了结构体中test方法地址直接调用

extention中提供方法的默认实现

  • 协议可以通过extention的方式去实现定义的方法,实现之后,遵循协议的类可以不再实现该方法。

协议中未声明方法,分类中声明并实现

protocol BaseProtocol {
    
}

extension BaseProtocol {
    func test() {
        print("BaseProtocol")
    }
}

class TestClass: BaseProtocol {
    func test() {
        print("TestClass")
    }
}

var test: BaseProtocol = TestClass()
test.test()

var test1: TestClass = TestClass()
test1.test()

打印结果:

BaseProtocol

TestClass

  • 把当前的main.swift文件编译成main.sil文件。编译完成后找到main函数
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main4testAA12BaseProtocol_pvp  // id: %2
  %3 = global_addr @$s4main4testAA12BaseProtocol_pvp : $*BaseProtocol // users: %9, %7
  %4 = metatype $@thick TestClass.Type            // user: %6
  // function_ref TestClass.__allocating_init()
  %5 = function_ref @$s4main9TestClassCACycfC : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %8
  %7 = init_existential_addr %3 : $*BaseProtocol, $TestClass // user: %8
  store %6 to [init] %7 : $*TestClass             // id: %8
  %9 = begin_access [read] [dynamic] %3 : $*BaseProtocol // users: %12, %11
  %10 = alloc_stack $BaseProtocol                 // users: %17, %16, %13, %11
  copy_addr %9 to [initialization] %10 : $*BaseProtocol // id: %11
  end_access %9 : $*BaseProtocol                  // id: %12
  %13 = open_existential_addr immutable_access %10 : $*BaseProtocol to $*@opened("A1E0E9E8-59EE-11ED-8487-86E79974171B") BaseProtocol // users: %15, %15
  // function_ref BaseProtocol.test()
  %14 = function_ref @$s4main12BaseProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : BaseProtocol> (@in_guaranteed τ_0_0) -> () // user: %15
  %15 = apply %14<@opened("A1E0E9E8-59EE-11ED-8487-86E79974171B") BaseProtocol>(%13) : $@convention(method) <τ_0_0 where τ_0_0 : BaseProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %13
  destroy_addr %10 : $*BaseProtocol               // id: %16
  dealloc_stack %10 : $*BaseProtocol              // id: %17
  alloc_global @$s4main5test1AA9TestClassCvp      // id: %18
  %19 = global_addr @$s4main5test1AA9TestClassCvp : $*TestClass // users: %24, %23
  %20 = metatype $@thick TestClass.Type           // user: %22
  // function_ref TestClass.__allocating_init()
  %21 = function_ref @$s4main9TestClassCACycfC : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %22
  %22 = apply %21(%20) : $@convention(method) (@thick TestClass.Type) -> @owned TestClass // user: %23
  store %22 to [init] %19 : $*TestClass           // id: %23
  %24 = begin_access [read] [dynamic] %19 : $*TestClass // users: %26, %25
  %25 = load [copy] %24 : $*TestClass             // users: %29, %28, %27
  end_access %24 : $*TestClass                    // id: %26
  %27 = class_method %25 : $TestClass, #TestClass.test : (TestClass) -> () -> (), $@convention(method) (@guaranteed TestClass) -> () // user: %28
  %28 = apply %27(%25) : $@convention(method) (@guaranteed TestClass) -> ()
  destroy_value %25 : $TestClass                  // id: %29
  %30 = integer_literal $Builtin.Int32, 0         // user: %31
  %31 = struct $Int32 (%30 : $Builtin.Int32)      // user: %32
  return %31 : $Int32                             // id: %32
} // end sil function 'main'
  • 我们可以看到在%14,静态类型为协议类型,通过函数地址直接调用。
  • 我们可以看到在%21,静态类型为类类型,则是通过VTable调用。

协议中声明方法,分类中实现

protocol BaseProtocol {
    func test()
}

extension BaseProtocol {
    func test() {
        print("BaseProtocol")
    }
}

class TestClass: BaseProtocol {
    func test() {
        print("TestClass")
    }
}

var test: BaseProtocol = TestClass()
test.test()

var test1: TestClass = TestClass()
test1.test()

打印结果:

TestClass

TestClass

  • 在协议中声明了函数,并且在分类中实现了该函数,会优先调用类中的方法。

协议的结构

  • 通过上面的例子,我们发现静态类型不同,会影响函数调用,是不是会影响实例对象的结构呢?
protocol BaseProtocol {
    func test()
}

extension BaseProtocol {
    func test() {
    }
}

class TestClass: BaseProtocol {
    var x: Int = 10
    func test() {
        
    }
}


var test: BaseProtocol = TestClass()

var test1: TestClass = TestClass()

print("test size: \(MemoryLayout.size(ofValue: test))")
print("test1 size: \(MemoryLayout.size(ofValue: test1))")

打印结果:

test size: 40

test1 size: 8

  • 我们发现静态类型不同,居然会影响内存中的大小。test1的8字节很好理解,是实例对象的地址。 那我们就再分析下这个40字节的内容了。

[图片上传失败...(image-a9fb64-1684500471316)]

  • 通过内存分析我们可以看到:

    • 第一个8字节存储着实例对象的地址
    • 第二个和第三个8字节存储的是啥目前为止。
    • 第四个8字节存储的是实例对象的metadate
    • 最后的8字节存储的其实是witness_table的地址。
  • 为什么说最后8字节就是witness_table的地址呢?打开汇编调试,找到test创建后witness_table相关的代码

[图片上传失败...(image-8e3b77-1684500471316)]

  • 如图所有最后8字节是witness_table地址。

witness_table的结构

  • 在上面我们知道了witness_table在内存中存储的位置,那么这个结构是这么样的呢?

  • 接下来我们就直接将当前的main.swift文件编译成main.ll文件。避免干扰,把test1变量和打印注释了

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  // 获取TestClass的metadata
  %3 = call swiftcc %swift.metadata_response @"type metadata accessor for main.TestClass"(i64 0) #7
  %4 = extractvalue %swift.metadata_response %3, 0
  // %swift.type = type { i64 }
  // %swift.refcounted = type { %swift.type*, i64 }
  // %T4main9TestClassC = type <{ %swift.refcounted, %TSd }>
  // 创建Test实例
  %5 = call swiftcc %T4main9TestClassC* @"main.TestClass.__allocating_init() -> main.TestClass"(%swift.type* swiftself %4)
  
  // %T4main12BaseProtocolP = type { [24 x i8], %swift.type*, i8** },%T4main12BaseProtocolP 本质上是一个结构体
  // 注意看,getelementptr为获取结构体成员,i32 0 结构体的内存地址,拿到这个结构体后将 %4 存储到这个结构体的第二个成员变量上
  // 也就是将 metadata 存储到这个结构体的第二个成员变量上,此时这个结构体的结构为:{ [24 x i8], metadata, i8** }
  store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main12BaseProtocolP, %T4main12BaseProtocolP* @"main.test : main.BaseProtocol", i32 0, i32 1), align 8

  // 这一行在获取 witness table,然后将 witness table 存储到 %T4main12BaseProtocolP 这个结构体的第三个成员变量上(因为取的是 i32 2)
  // 此时 %T4main5ShapeP 的结构为:{ [24 x i8], metadata, witness_table }
  store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"protocol witness table for main.TestClass : main.BaseProtocol in main", i32 0, i32 0), i8*** getelementptr inbounds (%T4main12BaseProtocolP, %T4main12BaseProtocolP* @"main.test : main.BaseProtocol", i32 0, i32 2), align 8
  
  // [24 x i8] 是 24 个 Int8 数组,内存中等价 [3 x i64] 数组,等价于 %T4main5ShapeP = type { [3 x i64], %swift.type*, i8** }
  // 这里是将 %T4main5ShapeP 这个结构体强制转换成 %T4main9TestClassC,此时的结构为:{ [3 x i64], metadata, witness_table }
  // 然后把 %5 存放到 %T4main12BaseProtocolP 的第一个元素。所以最后的结构为:{ [%T4main9TestClassC*, i64, i64], metadata, witness_table },    
  store %T4main9TestClassC* %5, %T4main9TestClassC** bitcast (%T4main12BaseProtocolP* @"main.test : main.BaseProtocol" to %T4main9TestClassC**), align 8
  ret i32 0
}
  • 接下来我们查看一下witness_table的内存结构
@"protocol witness table for main.TestClass : main.BaseProtocol in main" = hidden constant [2 x i8*] 
    [i8* bitcast (%swift.protocol_conformance_descriptor* @"protocol conformance descriptor for main.TestClass : main.BaseProtocol in main" to i8*), 
    i8* bitcast (void (%T4main9TestClassC**, %swift.type*, i8**)* @"protocol witness for main.BaseProtocol.test() -> () in conformance main.TestClass : main.BaseProtocol in main" to i8*)], 
    align 8
  • 可以看到这个结构中有两个成员,第一个成员是描述信息,第二个成员是test协议方法地址。
  • 下面我们通过源码来分析witness_table
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成员变量,我们查看一下它的类型
template <typename Runtime>
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;
}
  • 它有四个成员变量,描述了witness_table的一些基本信息。
  • 我们看Protocol这个成员变量,它是一个相对类型指针,其类型的结构为 TargetProtocolDescriptor
template<typename Runtime>
struct TargetProtocolDescriptor final
    : TargetContextDescriptor<Runtime>,
      swift::ABI::TrailingObjects<
        TargetProtocolDescriptor<Runtime>,
        TargetGenericRequirementDescriptor<Runtime>,
        TargetProtocolRequirement<Runtime>>
{
  // 省略部分方法
private:
  /// 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;

  // 省略部分方法
}
  • 内部一些属性,描述了协议的名称、关联类型等一些信息

  • 总结:

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

Existential Container-存在容器

  • Existential container是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协议类型,因为这些类型的内存大小不一致,所以通过当前的Existential Container统一做管理
  • 它遵循两个原则
    • 对于小容量的数据,直接存储在Value Buffer (小于等于24字节)
    • 对于大容量的数据,通过堆区分配,存储堆空间的地址
  • 协议的结构就是存在容器,这个存在容器最后的两个 8 字节存储的内容是固定的,存储的是这个实例类型的元类型和协议的见证表。
  • 那这前3个8字节存了什么?
    • 若对象是引用类型实例,则前8 字节是实例地址的信息
    • 若对象是值类型实例,则前24 字节是属性值信息,或者前8 字节是存放属性值的地址空间地址信息
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 一:协议 1.1 协议的定义 协议可以用来定义 方法、属性 、下标的声明 ,协议可以被 枚举、结构体、类遵守(多个...
    Lee_Toto阅读 467评论 0 1
  • Protocol:所谓协议,就是一组属性和/或方法的定义,而如果某个具体类型想要遵守一个协议,那它需要实现这个协议...
    帅驼驼阅读 521评论 4 3
  • 一.协议与继承 那么最直观也是最简单的办法就是,给每一个类添加一个debug函数 如果我们对当前代码中的每个类都需...
    MissStitch丶阅读 455评论 0 2
  • Swift — 协议(Protocol) [TOC] 协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法...
    just东东阅读 1,932评论 1 2
  • 协议方法的调度 protocol Incrementable{ var age: Int {set get} ...
    张天宇_bba7阅读 198评论 0 2