0. 引言
下面的代码输出什么?(摘自objc.io twitter的swift quiz)
protocol Drawing {
func render()
}
extension Drawing {
func circle() { print("protocol") }
func render() { circle() }
}
class SVG: Drawing {
func circle() { print("class") }
}
SVG().render()
// what's the output?
答案是:protocol 。
原因是 extension中声明的函数是静态派发,编译的时候就已经确定了调用地址,类无法重写实现。
我们通过 SIL 分析一下:
swiftc -emit-silgen -O demo.swift -o demo.sil
- demo.sil
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
%2 = metatype $@thick SVG.Type // user: %4
// function_ref SVG.__allocating_init()
%3 = function_ref @$s4demo3SVGCACycfC : $@convention(method) (@thick SVG.Type) -> @owned SVG // user: %4
%4 = apply %3(%2) : $@convention(method) (@thick SVG.Type) -> @owned SVG // user: %6
%5 = alloc_stack $SVG // users: %10, %9, %8, %6
store %4 to [init] %5 : $*SVG // id: %6
// function_ref Drawing.render()
%7 = function_ref @$s4demo7DrawingPAAE6renderyyF : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> () // user: %8
%8 = apply %7<SVG>(%5) : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> ()
destroy_addr %5 : $*SVG // id: %9
dealloc_stack %5 : $*SVG // id: %10
%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'
我们可以看到SVG初始化后,是直接调用 Drawing.render() 协议的静态函数的。
// Drawing.render()
sil hidden [ossa] @$s4demo7DrawingPAAE6renderyyF : $@convention(method) <Self where Self : Drawing> (@in_guaranteed Self) -> () {
// %0 "self" // users: %3, %1
bb0(%0 : $*Self):
debug_value_addr %0 : $*Self, let, name "self", argno 1 // id: %1
// function_ref Drawing.circle()
%2 = function_ref @$s4demo7DrawingPAAE6circleyyF : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> () // user: %3
%3 = apply %2<Self>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : Drawing> (@in_guaranteed τ_0_0) -> ()
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function '$s4demo7DrawingPAAE6renderyyF'
而对于 Drawing.render() 来说,内部也只直接调用 Drawing.circle() 的,所以这是编译期就决定了的。
了解Swift函数的派发机制,有助于你理解Swift中函数的调用过程,解决一些 “莫名其妙” 的bug。
1. 派发机制
函数派发的三种类型
- 静态派发
- 函数表派发
- 消息派发
1.1 静态派发
静态派发是三种派发方式中最快的。CPU 直接拿到函数地址并进行调用。编译器优化时,也常常将函数进行内联,将其转换为静态派发方式,提升执行速度。
C++ 默认使用静态派发;在 Swift 中给函数加上final关键字,也会变成静态派发。
优点:
- 使用最少的指令集,办最快的事情。
缺点:
- 静态派发最大的弊病就是没有动态性,不支持继承。
1.2 函数表派发
编译型语言中最常见的派发方式,既保证了动态性也兼顾了执行效率。
函数所在的类会维护一个“函数表”(虚函数表),存取了每个函数实现的指针。
每个类的 vtable 在编译时就会被构建,所以与静态派发相比多出了两个读取的工作:
- 读取该类的 vtable
- 读取函数的指针
优点:
- 查表是一种简单,易实现,而且性能可预知的方式。
- 理论上说,函数表派发也是一种高效的方式。
缺点:
- 与静态派发相比,从字节码角度来看,多了两次读和一次跳转。
- 与静态派发相比,编译器对某些含有副作用的函数无法优化。
- Swift 类扩展里面的函数无法动态加入该类的函数表中,只能使用静态派发的方式。
举个例子(只是一个示例):
class A {
func method1() {}
}
class B: A {
func method2() {}
}
class C: B {
override func method2() {}
func method3() {}
}
offset | 0xA00 | A | 0xB00 | B | 0xC00 | C |
0 | 0x121 | A.method1 | 0x121 | A.method1 | 0x121 | A.method1 |
1 | 0x222 | B.method2 | 0x322 | C.method2 | ||
2 | 0x323 | C.method3 |
let obj = C()
obj.method2()
当method2被调用时,会经历下面的几个过程:
- 读取对象 0xC00 的函数表
- 读取函数指针的索引, method2 的地址为0x322
- 跳转执行 0x322
1.3 消息派发
消息机制是调用函数最动态的方式。由于 Swfit 使用的依旧是 Objective-C 的运行时系统,消息派发其实也就是 Objective-C 的 Message Passing(消息传递)。由于消息传递大家看的文章很多了,这里不做过多赘述。
id returnValue = [obj messageName:param];
// 底层代码
id returnValue = objc_msgSend(obj, @selector(messageName:), param);
优点:
- 动态性高
- Method Swizzling
- isa Swizzling
- ...
缺点:
- 执行效率是三种派发方式中最低的
所幸的是 objc_msgSend
会将匹配的结果缓存到一个映射表中,每个类都有这样一块缓存。若是之后发送相同的消息,执行速率会很快。
2. Swift的派发机制
Swift的派发机制受到4个因素的影响:
- 数据类型
- 函数声明的位置
- 指定派发方式
- 编译器优化
2.1 数据类型
类型 | 初始声明 | 扩展 |
值类型 | 静态派发 | 静态派发 |
协议 | 函数表派发 | 静态派发 |
类 | 函数表派发 | 静态派发 |
NSObject子类 | 函数表派发 | 静态派发 |
class MyClass {
func testOfClass() {}
}
struct MyStruct {
func testOfStruct() {}
}
我们来看看 SIL 的结果:
class MyClass {
func testOfClass()
@objc deinit
init()
}
struct MyStruct {
func testOfStruct()
init()
}
// MyClass.testOfClass()
sil hidden [ossa] @$s4demo7MyClassC06testOfC0yyF : $@convention(method) (@guaranteed MyClass) -> () {
...
} // end sil function '$s4demo7MyClassC06testOfC0yyF'
// MyClass.deinit
sil hidden [ossa] @$s4demo7MyClassCfd : $@convention(method) (@guaranteed MyClass) -> @owned Builtin.NativeObject {
...
} // end sil function '$s4demo7MyClassCfd'
// MyClass.__deallocating_deinit
sil hidden [ossa] @$s4demo7MyClassCfD : $@convention(method) (@owned MyClass) -> () {
...
} // end sil function '$s4demo7MyClassCfD'
// MyClass.__allocating_init()
sil hidden [exact_self_class] [ossa] @$s4demo7MyClassCACycfC : $@convention(method) (@thick MyClass.Type) -> @owned MyClass {
...
} // end sil function '$s4demo7MyClassCACycfC'
// MyClass.init()
sil hidden [ossa] @$s4demo7MyClassCACycfc : $@convention(method) (@owned MyClass) -> @owned MyClass {
...
} // end sil function '$s4demo7MyClassCACycfc'
// MyStruct.testOfStruct()
sil hidden [ossa] @$s4demo8MyStructV06testOfC0yyF : $@convention(method) (MyStruct) -> () {
...
} // end sil function '$s4demo8MyStructV06testOfC0yyF'
// MyStruct.init()
sil hidden [ossa] @$s4demo8MyStructVACycfC : $@convention(method) (@thin MyStruct.Type) -> MyStruct {
...
} // end sil function '$s4demo8MyStructVACycfC'
sil_vtable MyClass {
#MyClass.testOfClass: (MyClass) -> () -> () : @$s4demo7MyClassC06testOfC0yyF // MyClass.testOfClass()
#MyClass.init!allocator: (MyClass.Type) -> () -> MyClass : @$s4demo7MyClassCACycfC // MyClass.__allocating_init()
#MyClass.deinit!deallocator: @$s4demo7MyClassCfD // MyClass.__deallocating_deinit
}
我们抛开函数具体的实现,可以看到
- struct 类型仅使用静态派发,不存在 vtable 结构;
- class 类型存在 vtable 结构,函数依次被存放在 vtable 中,使用函数表派发。
2.2 函数声明的位置
函数声明位置的不同也会导致派发方式的不同。
- 在 类 中声明
- 在 扩展 中声明
protocol MyProtocol {
func testOfProtocol()
}
extension MyProtocol {
func testOfProtocolInExtension() {}
}
class MyClass: MyProtocol {
func testOfClass() {}
func testOfProtocol() {}
}
extension MyClass {
func testOfClassInExtension() {}
}
我们来看看 SIL 的结果:
protocol MyProtocol {
func testOfProtocol()
}
extension MyProtocol {
func testOfProtocolInExtension()
}
class MyClass : MyProtocol {
func testOfClass()
func testOfProtocol()
@objc deinit
init()
}
extension MyClass {
func testOfClassInExtension()
}
// MyProtocol.testOfProtocolInExtension()
sil hidden [ossa] @$s4demo10MyProtocolPAAE06testOfC11InExtensionyyF : $@convention(method) <Self where Self : MyProtocol> (@in_guaranteed Self) -> () {
...
} // end sil function '$s4demo10MyProtocolPAAE06testOfC11InExtensionyyF'
// MyClass.testOfClass()
sil hidden [ossa] @$s4demo7MyClassC06testOfC0yyF : $@convention(method) (@guaranteed MyClass) -> () {
...
} // end sil function '$s4demo7MyClassC06testOfC0yyF'
// MyClass.testOfProtocol()
sil hidden [ossa] @$s4demo7MyClassC14testOfProtocolyyF : $@convention(method) (@guaranteed MyClass) -> () {
...
} // end sil function '$s4demo7MyClassC14testOfProtocolyyF'
// MyClass.deinit
sil hidden [ossa] @$s4demo7MyClassCfd : $@convention(method) (@guaranteed MyClass) -> @owned Builtin.NativeObject {
...
} // end sil function '$s4demo7MyClassCfd'
// MyClass.__deallocating_deinit
sil hidden [ossa] @$s4demo7MyClassCfD : $@convention(method) (@owned MyClass) -> () {
...
} // end sil function '$s4demo7MyClassCfD'
// MyClass.__allocating_init()
sil hidden [exact_self_class] [ossa] @$s4demo7MyClassCACycfC : $@convention(method) (@thick MyClass.Type) -> @owned MyClass {
...
} // end sil function '$s4demo7MyClassCACycfC'
// MyClass.init()
sil hidden [ossa] @$s4demo7MyClassCACycfc : $@convention(method) (@owned MyClass) -> @owned MyClass {
...
} // end sil function '$s4demo7MyClassCACycfc'
// protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
sil private [transparent] [thunk] [ossa] @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed MyClass) -> () {
...
} // end sil function '$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW'
// MyClass.testOfClassInExtension()
sil hidden [ossa] @$s4demo7MyClassC06testOfC11InExtensionyyF : $@convention(method) (@guaranteed MyClass) -> () {
...
} // end sil function '$s4demo7MyClassC06testOfC11InExtensionyyF'
sil_vtable MyClass {
#MyClass.testOfClass: (MyClass) -> () -> () : @$s4demo7MyClassC06testOfC0yyF // MyClass.testOfClass()
#MyClass.testOfProtocol: (MyClass) -> () -> () : @$s4demo7MyClassC14testOfProtocolyyF // MyClass.testOfProtocol()
#MyClass.init!allocator: (MyClass.Type) -> () -> MyClass : @$s4demo7MyClassCACycfC // MyClass.__allocating_init()
#MyClass.deinit!deallocator: @$s4demo7MyClassCfD // MyClass.__deallocating_deinit
}
sil_witness_table hidden MyClass: MyProtocol module demo {
method #MyProtocol.testOfProtocol: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW // protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
}
我们抛开函数具体的实现,可以看到
- 声明在 协议 或者 类 中的函数是使用函数表派发的
- 声明在 扩展 中的函数则是静态派发
此外,我们可以看到,MyClass 实现 MyProtocol 的 testOfProtocol 在 sil_witness_table 中的函数地址对应的实现。
// protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
sil private [transparent] [thunk] [ossa] @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed MyClass) -> () {
// %0 // user: %1
bb0(%0 : $*MyClass):
%1 = load_borrow %0 : $*MyClass // users: %5, %3, %2
%2 = class_method %1 : $MyClass, #MyClass.testOfProtocol : (MyClass) -> () -> (), $@convention(method) (@guaranteed MyClass) -> () // user: %3
%3 = apply %2(%1) : $@convention(method) (@guaranteed MyClass) -> ()
%4 = tuple () // user: %6
end_borrow %1 : $MyClass // id: %5
return %4 : $() // id: %6
} // end sil function '$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW'
sil_witness_table hidden MyClass: MyProtocol module demo {
method #MyProtocol.testOfProtocol: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW // protocol witness for MyProtocol.testOfProtocol() in conformance MyClass
}
可以看到,通过 testOfProtocol 的具体实现 @$s4demo7MyClassCAA0B8ProtocolA2aDP06testOfD0yyFTW 我们可以看到,在其内部还是执行的MyClass的 MyClass.testOfProtocol 函数。
即,无论是通过协议,还是通过类进行访问,最终都访问的是 MyClass.testOfProtocol 函数。
2.3 指定派发方式
给函数添加关键字的修饰也会改变其派发方式。
- final
添加了 final 关键字的函数无法被重写,使用静态派发,不会在 vtable 中出现,且对 objc 运行时不可见。
class Test {
final func foo() {}
}
Test().foo()
sil_vtable Test {
#Test.init!allocator: (Test.Type) -> () -> Test : @$s4demo4TestCACycfC // Test.__allocating_init()
#Test.deinit!deallocator: @$s4demo4TestCfD // Test.__deallocating_deinit
}
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
%2 = metatype $@thick Test.Type // user: %4
// function_ref Test.__allocating_init()
%3 = function_ref @$s4demo4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test // user: %4
%4 = apply %3(%2) : $@convention(method) (@thick Test.Type) -> @owned Test // users: %7, %6
// function_ref Test.foo()
%5 = function_ref @$s4demo4TestC3fooyyF : $@convention(method) (@guaranteed Test) -> () // user: %6
%6 = apply %5(%4) : $@convention(method) (@guaranteed Test) -> ()
destroy_value %4 : $Test // id: %7
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
return %9 : $Int32 // id: %10
} // end sil function 'main'
final 关键字会将函数变为静态派发,不会在 vtable 中出现。从 main 函数中的调用 function_ref 也可以看得出。
- dynamic
函数均可添加 dynamic 关键字,为非objc类和值类型的函数赋予动态性,但派发方式还是函数表派发。
下面这个例子展示了如何利用 dynamic 关键字,实现 Method Swizzling 。
class Test {
dynamic func foo() {
print("bar")
}
}
extension Test {
@_dynamicReplacement(for: foo())
func foo_new() {
print("bar new")
}
}
Test().foo() // bar new
- @objc
该关键字可以将Swift函数暴露给Objc运行时,但并不会改变其派发方式,依旧是函数表派发。
class Test {
@objc func foo() {}
}
// Test.foo()
sil hidden [ossa] @$s4demo4TestC3fooyyF : $@convention(method) (@guaranteed Test) -> () {
// %0 "self" // user: %1
bb0(%0 : @guaranteed $Test):
debug_value %0 : $Test, let, name "self", argno 1 // id: %1
%2 = tuple () // user: %3
return %2 : $() // id: %3
} // end sil function '$s4demo4TestC3fooyyF'
// @objc Test.foo()
sil hidden [thunk] [ossa] @$s4demo4TestC3fooyyFTo : $@convention(objc_method) (Test) -> () {
// %0 // user: %1
bb0(%0 : @unowned $Test):
%1 = copy_value %0 : $Test // users: %6, %2
%2 = begin_borrow %1 : $Test // users: %5, %4
// function_ref Test.foo()
%3 = function_ref @$s4demo4TestC3fooyyF : $@convention(method) (@guaranteed Test) -> () // user: %4
%4 = apply %3(%2) : $@convention(method) (@guaranteed Test) -> () // user: %7
end_borrow %2 : $Test // id: %5
destroy_value %1 : $Test // id: %6
return %4 : $() // id: %7
} // end sil function '$s4demo4TestC3fooyyFTo'
...
sil_vtable Test {
#Test.foo: (Test) -> () -> () : @$s4demo4TestC3fooyyF // Test.foo()
#Test.init!allocator: (Test.Type) -> () -> Test : @$s4demo4TestCACycfC // Test.__allocating_init()
#Test.deinit!deallocator: @$s4demo4TestCfD // Test.__deallocating_deinit
}
foo 函数依然在 vtable 中,暴露给 Objec 的函数 @objc Test.foo() 其内部也是调用的 foo 函数。
- @objc + dynamic
class Test {
dynamic func foo1() {}
@objc func foo2() {}
@objc dynamic func foo3() {}
}
let text = Test()
text.foo1()
text.foo2()
text.foo3()
sil_vtable Test {
#Test.foo1: (Test) -> () -> () : @$s4demo4TestC4foo1yyF // Test.foo1()
#Test.foo2: (Test) -> () -> () : @$s4demo4TestC4foo2yyF // Test.foo2()
#Test.init!allocator: (Test.Type) -> () -> Test : @$s4demo4TestCACycfC // Test.__allocating_init()
#Test.deinit!deallocator: @$s4demo4TestCfD // Test.__deallocating_deinit
}
vtable 中只有 foo1 和 foo2 ,没有 foo3。
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4demo4textAA4TestCvp // id: %2
%3 = global_addr @$s4demo4textAA4TestCvp : $*Test // users: %16, %12, %8, %7
%4 = metatype $@thick Test.Type // user: %6
// function_ref Test.__allocating_init()
%5 = function_ref @$s4demo4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick Test.Type) -> @owned Test // user: %7
store %6 to [init] %3 : $*Test // id: %7
%8 = load_borrow %3 : $*Test // users: %11, %10, %9
%9 = class_method %8 : $Test, #Test.foo1 : (Test) -> () -> (), $@convention(method) (@guaranteed Test) -> () // user: %10
%10 = apply %9(%8) : $@convention(method) (@guaranteed Test) -> ()
end_borrow %8 : $Test // id: %11
%12 = load_borrow %3 : $*Test // users: %15, %14, %13
%13 = class_method %12 : $Test, #Test.foo2 : (Test) -> () -> (), $@convention(method) (@guaranteed Test) -> () // user: %14
%14 = apply %13(%12) : $@convention(method) (@guaranteed Test) -> ()
end_borrow %12 : $Test // id: %15
%16 = load_borrow %3 : $*Test // users: %19, %18, %17
%17 = objc_method %16 : $Test, #Test.foo3!foreign : (Test) -> () -> (), $@convention(objc_method) (Test) -> () // user: %18
%18 = apply %17(%16) : $@convention(objc_method) (Test) -> ()
end_borrow %16 : $Test // id: %19
%20 = integer_literal $Builtin.Int32, 0 // user: %21
%21 = struct $Int32 (%20 : $Builtin.Int32) // user: %22
return %21 : $Int32 // id: %22
} // end sil function 'main'
从 main 函数的调用来看,Test.foo1 和 Test.foo2 都是通过 class_method 采用 函数表 的方式。而 Test.foo3 则是通过 objc_method 采用 消息派发 的方式。
- @inline
class Test {
@inline(__always) func foo() {}
}
sil_vtable Test {
#Test.foo: (Test) -> () -> () : @$s4demo4TestC3fooyyF // Test.foo()
#Test.init!allocator: (Test.Type) -> () -> Test : @$s4demo4TestCACycfC // Test.__allocating_init()
#Test.deinit!deallocator: @$s4demo4TestCfD // Test.__deallocating_deinit
}
告诉编译器将此函数静态派发,但将其转换成SIL代码后,依旧是 vtable 派发。
- static
class Test {
static func foo() {}
}
Test.foo()
sil_vtable Test {
#Test.init!allocator: (Test.Type) -> () -> Test : @$s4demo4TestCACycfC // Test.__allocating_init()
#Test.deinit!deallocator: @$s4demo4TestCfD // Test.__deallocating_deinit
}
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
%2 = metatype $@thick Test.Type // user: %4
// function_ref static Test.foo()
%3 = function_ref @$s4demo4TestC3fooyyFZ : $@convention(method) (@thick Test.Type) -> () // user: %4
%4 = apply %3(%2) : $@convention(method) (@thick Test.Type) -> ()
%5 = integer_literal $Builtin.Int32, 0 // user: %6
%6 = struct $Int32 (%5 : $Builtin.Int32) // user: %7
return %6 : $Int32 // id: %7
} // end sil function 'main'
static 关键字会将函数变为静态派发,不会在 vtable 中出现。从 main 函数中的调用 function_ref 也可以看得出。
- 总结
类型 | 静态派发 | 函数表派发 | 消息派发 |
值类型 | 所有方法 | / | / |
协议 | extension | 主体创建 | / |
类 | extension/final/static | 主体创建 | @objc + dynamic |
NSObject子类 | extension/final/static | 主体创建 | @objc + dynamic |
除此之外,编译器可能将某些方法优化为静态派发。例如,私有函数。
2.4 编译器优化
Swift 会尽可能的去优化函数派发方式。当一个类声明了一个私有函数时,该函数很可能会被优化为静态派发。
这也就是为什么在 Swift 中使用 target-action 模式时,私有 selector 会报错的原因(Objective-C 无法获取 #selector 指定的函数)。
另一个需要注意的是,NSObject子类 中的 属性,如果没有使用 dynamic 修饰的话, 这个优化会默认让 KVO 失效。因为,这个属性的 getter 和 setter 会被优化为静态派发。虽然,代码可以通过编译,不过动态生成的 KVO 函数就不会被触发。
3. 面试题2
下面的代码输出什么?
protocol Logger {
func log(content: String)
}
extension Logger{
func log(content: String){
print(content)
}
func description()->String{
return "Logger"
}
}
class MyLogger:Logger{
func log(content: String) {
print("MyLogger: " + content)
}
func description()->String{
return "MyLogger"
}
}
let p1:Logger = MyLogger()
p1.log(content: "p1")
print(p1.description())
let p2:MyLogger = MyLogger()
p2.log(content: "p2")
print(p2.description())
答案:
let p1:Logger = MyLogger()
p1.log(content: "p1") // MyLogger: p1
print(p1.description()) // Logger
let p2:MyLogger = MyLogger()
p2.log(content: "p2") // MyLogger: p2
print(p2.description()) // MyLogger
为什么会产生这种结果呢?
sil_vtable MyLogger {
#MyLogger.log: (MyLogger) -> (String) -> () : @$s4demo8MyLoggerC3log7contentySS_tF // MyLogger.log(content:)
#MyLogger.description: (MyLogger) -> () -> String : @$s4demo8MyLoggerC11descriptionSSyF // MyLogger.description()
#MyLogger.init!allocator: (MyLogger.Type) -> () -> MyLogger : @$s4demo8MyLoggerCACycfC // MyLogger.__allocating_init()
#MyLogger.deinit!deallocator: @$s4demo8MyLoggerCfD // MyLogger.__deallocating_deinit
}
sil_witness_table hidden MyLogger: Logger module demo {
method #Logger.log: <Self where Self : Logger> (Self) -> (String) -> () : @$s4demo8MyLoggerCAA0C0A2aDP3log7contentySS_tFTW // protocol witness for Logger.log(content:) in conformance MyLogger
}
/* ⚠️**重点** */
// protocol witness for Logger.log(content:) in conformance MyLogger
sil private [transparent] [thunk] [ossa] @$s4demo8MyLoggerCAA0C0A2aDP3log7contentySS_tFTW : $@convention(witness_method: Logger) (@guaranteed String, @in_guaranteed MyLogger) -> () {
// %0 // user: %4
// %1 // user: %2
bb0(%0 : @guaranteed $String, %1 : $*MyLogger):
%2 = load_borrow %1 : $*MyLogger // users: %6, %4, %3
%3 = class_method %2 : $MyLogger, #MyLogger.log : (MyLogger) -> (String) -> (), $@convention(method) (@guaranteed String, @guaranteed MyLogger) -> () // user: %4
%4 = apply %3(%0, %2) : $@convention(method) (@guaranteed String, @guaranteed MyLogger) -> ()
%5 = tuple () // user: %7
end_borrow %2 : $MyLogger // id: %6
return %5 : $() // id: %7
} // end sil function '$s4demo8MyLoggerCAA0C0A2aDP3log7contentySS_tFTW'
由于 Swift 是强类型的语言,所以 p1 为 Logger 类型, p2 为 MyLogger 类型。
protocol 通过 sil_witness_table 进行调用。sil_witness_table 中的 Logger.log 函数,内部实现为通过 class_method 调用 MyLogger.log ,所以无论是 p1 还是 p2 均正常输出 MyLogger: p1/p2 。
而对于 description 函数会有一些不同:
- 对于 p1 Logger 类型来说,这是一个静态派发的函数,所以输出 Logger;
- 对于 p2 MyLogger 类型来说,这是一个 vtabel 派发的函数,所以输出 MyLogger。
分析 main 函数,也可以得出相同的结论:
// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
...
// function_ref Logger.description()
%26 = function_ref @$s4demo6LoggerPAAE11descriptionSSyF : $@convention(method) <τ_0_0 where τ_0_0 : Logger> (@in_guaranteed τ_0_0) -> @owned String // user: %27
...
%63 = class_method %62 : $MyLogger, #MyLogger.description : (MyLogger) -> () -> String,
} // end sil function 'main'
- 对于 p1 Logger 类型来说,通过 function_ref 进行调用,为静态派发;
- 对于 p2 MyLogger 类型来说,通过 class_method 进行调用,为函数表派发。