swift的方法派发
方法派发方式
方法派发方式的意思就是:怎么找到方法的app运行时,方法会被存在内存中的某处,只要能找到方法的地址,就可以执行这个方法,所以也就是怎么找到方法地址的方式
在swift中,却有3种:
直接派发:就是在编译时,调用某个方法的话,就直接把这个方法的地址也进去了,这是没有任何动态性可言的,但它是最快的,因为不需要任何查找的过程
在swift中,苹果没有继续采用消息派发
函数表派发:如图,本质上它是一个数组,里面存着函数指针
这种派发方式效率上比消息派发的cache还快一点,因为它是个数组,直接在调用的地方写上这个方法是数组中的第几个方法,cache是个哈希表,还是需要找的
消息派发:就是OC的那套
直接派发就是静态派发,消息和函数表都是动态派发,静态是在编译时就决定了,动态派发是在运行决定的
在编译时决定的,是依靠什么决定的?是依靠声明类型,运行时决定是靠实际类型决定的
所以只有在没有多态的情况下,才能使用直接派发,举个例子:
父类型有个方法a,子类覆写了方法a,然后子类型的实例被声明成父类型
如果这种情况下,使用直接派发,那就有问题了,编译器以为你想调用父类型的方法a,但其实你想调用子类型的方法a
所以多态的情况下,就不能使用直接派发
SIL
SIL,这是LLVM编译器的中间语言
苹果在github中有对这个语言语法的完整文档
这是结构体Struct的空方法 a 的SIL实现
- 我定义的方法 a 中并没有参数,但是 SIL 却有一个类型为 Struct 的参数,其实就是 self,swift 是通过参数的方式获取到 self 的
- 方法 a 的名字由 文件名 Simple,结构体名 Struct,方法名 a 等拼接在一起组成:@Simple.Struct.a() -> ()
- SIL 用冒号将对象与类型分隔开,所以方法的类型就是:$@convention(method) (Struct) -> ()
- 方法的每一行都会有一个返回值,并由 %n 接收
- bb0 是 label 名字,当有分支的时候,是通过 label 安排代码的
- swift 中的 () 就是 Void,也就是一个空元组,所以返回了 %2 也就是 tuple()
各种情况下的派发方式
使用命令 swiftc -emit-sil ClassFunc.swift | xcrun swift-demangle > ClassFunc.silgen 将demo转成SIL查看
结构体/swift类/oc类 在 主类/extension 中如何派发
// 结构体/swift类/oc类 在 主类/extension 中如何派发 import Foundation struct Struct { func a() { } } extension Struct { func b() { } } class SwiftClass { func c() { } } extension SwiftClass { func d() { } func h() { let s = Struct() let sc = SwiftClass() s.a() s.b() sc.c() sc.d() } }
以下是转成SIL后的代码,我只保留其中重要的部分,也就是方法h,h中是对各个方法的调用:
metatype是拿到这个类
function_ref是直接获取这个函数的地址
apply是调用这个函数
// SwiftClass.h() sil hidden @Simple.SwiftClass.h() -> () : $@convention(method) (@guaranteed SwiftClass) -> () { // %0 // user: %1 bb0(%0 : $SwiftClass): debug_value %0 : $SwiftClass, let, name "self", argno 1 // id: %1 %2 = metatype $@thin Struct.Type // user: %4 // function_ref Struct.init() %3 = function_ref @Simple.Struct.init() -> Simple.Struct : $@convention(method) (@thin Struct.Type) -> Struct // user: %4 %4 = apply %3(%2) : $@convention(method) (@thin Struct.Type) -> Struct // users: %13, %11, %5 debug_value %4 : $Struct, let, name "s" // id: %5 %6 = metatype $@thick SwiftClass.Type // user: %8 // function_ref SwiftClass.__allocating_init() %7 = function_ref @Simple.SwiftClass.__allocating_init() -> Simple.SwiftClass : $@convention(method) (@thick SwiftClass.Type) -> @owned SwiftClass // user: %8 %8 = apply %7(%6) : $@convention(method) (@thick SwiftClass.Type) -> @owned SwiftClass // users: %18, %17, %14, %15, %9 debug_value %8 : $SwiftClass, let, name "sc" // id: %9 // function_ref Struct.a() %10 = function_ref @Simple.Struct.a() -> () : $@convention(method) (Struct) -> () // user: %11 %11 = apply %10(%4) : $@convention(method) (Struct) -> () // function_ref Struct.b() %12 = function_ref @Simple.Struct.b() -> () : $@convention(method) (Struct) -> () // user: %13 %13 = apply %12(%4) : $@convention(method) (Struct) -> () %14 = class_method %8 : $SwiftClass, #SwiftClass.c!1 : (SwiftClass) -> () -> (), $@convention(method) (@guaranteed SwiftClass) -> () // user: %15 %15 = apply %14(%8) : $@convention(method) (@guaranteed SwiftClass) -> () // function_ref SwiftClass.d() %16 = function_ref @Simple.SwiftClass.d() -> () : $@convention(method) (@guaranteed SwiftClass) -> () // user: %17 %17 = apply %16(%8) : $@convention(method) (@guaranteed SwiftClass) -> () strong_release %8 : $SwiftClass // id: %18 %19 = tuple () // user: %20 return %19 : $() // id: %20 } // end sil function 'Simple.SwiftClass.h() -> ()' sil_vtable SwiftClass { #SwiftClass.c!1: (SwiftClass) -> () -> () : @Simple.SwiftClass.c() -> () // SwiftClass.c() #SwiftClass.init!allocator.1: (SwiftClass.Type) -> () -> SwiftClass : @Simple.SwiftClass.__allocating_init() -> Simple.SwiftClass // SwiftClass.__allocating_init() #SwiftClass.deinit!deallocator.1: @Simple.SwiftClass.__deallocating_deinit // SwiftClass.__deallocating_deinit }
可以得出结论:
结构体都是直接派发,结构体是不支持继承的,所以在不考虑协议的情况下,结构体是没有多态的,所以就直接派发
swift类和oc类是一样的,主类是虚函数表,extension是直接派发
我的猜测是这样的:
在oc中category的方法是在运行时加入到类的方法列表中的,调用方法的时候是需要查找的
swift用的是虚函数表,调用函数时是通过指定调用虚函数表的第几个方法而实现的,这样的话,就不能在运行时动态插入,不然会乱掉
那就只能用直接派发了
所以只要是在extension中,就不可能出现虚函数表派发
也因此,extension中的方法不能被子类覆写,因为直接派发不支持多态
final/private
// final/private import Foundation class SwiftClass { final func a() { } private func b() { } } extension SwiftClass { final func ae() { } private func be() { } func j() { let sc = SwiftClass() sc.a() sc.b() sc.ae() sc.be() } }
SIL:
// SwiftClass.j() sil hidden @Simple.SwiftClass.j() -> () : $@convention(method) (@guaranteed SwiftClass) -> () { // %0 // user: %1 bb0(%0 : $SwiftClass): debug_value %0 : $SwiftClass, let, name "self", argno 1 // id: %1 %2 = metatype $@thick SwiftClass.Type // user: %4 // function_ref SwiftClass.__allocating_init() %3 = function_ref @Simple.SwiftClass.__allocating_init() -> Simple.SwiftClass : $@convention(method) (@thick SwiftClass.Type) -> @owned SwiftClass // user: %4 %4 = apply %3(%2) : $@convention(method) (@thick SwiftClass.Type) -> @owned SwiftClass // users: %14, %13, %11, %9, %7, %5 debug_value %4 : $SwiftClass, let, name "sc" // id: %5 // function_ref SwiftClass.a() %6 = function_ref @Simple.SwiftClass.a() -> () : $@convention(method) (@guaranteed SwiftClass) -> () // user: %7 %7 = apply %6(%4) : $@convention(method) (@guaranteed SwiftClass) -> () // function_ref SwiftClass.b() %8 = function_ref @Simple.SwiftClass.(b in _6A05BE928C3561213BB32E46B467BA0F)() -> () : $@convention(method) (@guaranteed SwiftClass) -> () // user: %9 %9 = apply %8(%4) : $@convention(method) (@guaranteed SwiftClass) -> () // function_ref SwiftClass.ae() %10 = function_ref @Simple.SwiftClass.ae() -> () : $@convention(method) (@guaranteed SwiftClass) -> () // user: %11 %11 = apply %10(%4) : $@convention(method) (@guaranteed SwiftClass) -> () // function_ref SwiftClass.be() %12 = function_ref @Simple.SwiftClass.(be in _6A05BE928C3561213BB32E46B467BA0F)() -> () : $@convention(method) (@guaranteed SwiftClass) -> () // user: %13 %13 = apply %12(%4) : $@convention(method) (@guaranteed SwiftClass) -> () strong_release %4 : $SwiftClass // id: %14 %15 = tuple () // user: %16 return %15 : $() // id: %16 } // end sil function 'Simple.SwiftClass.j() -> ()' sil_vtable SwiftClass { #SwiftClass.b!1: (SwiftClass) -> () -> () : @Simple.SwiftClass.(b in _6A05BE928C3561213BB32E46B467BA0F)() -> () // SwiftClass.b() #SwiftClass.init!allocator.1: (SwiftClass.Type) -> () -> SwiftClass : @Simple.SwiftClass.__allocating_init() -> Simple.SwiftClass // SwiftClass.__allocating_init() #SwiftClass.deinit!deallocator.1: @Simple.SwiftClass.__deallocating_deinit // SwiftClass.__deallocating_deinit }
这个看似非常好理解,但是如果被final的方法是从父类那里继承来的,并且父类并没有final的话,会采用什么派发方式?虚函数表中还有没有这个方法
// 从父类继承来的方法被子类final import Foundation class SwiftClass { func a() { } private func b() { } } extension SwiftClass { final func ae() { } private func be() { } func j() { let sc = SwiftClass() sc.a() sc.b() sc.ae() sc.be() } } class S: SwiftClass { final override func a() { } }
方法 a 将会变成虚函数表的派发方式
疑问🤔️:private的方法既然采用直接派发的方式,为什么还要存放在虚函数表中?
// @objc/dynamic import Foundation class SwiftClass { @objc func c() { } dynamic func d() { } } extension SwiftClass { @objc func ce() { } dynamic func de() { } func j() { let sc = SwiftClass() sc.c() sc.d() sc.ce() sc.de() } }
// SwiftClass.j() sil hidden @Simple.SwiftClass.j() -> () : $@convention(method) (@guaranteed SwiftClass) -> () { // %0 // user: %1 bb0(%0 : $SwiftClass): debug_value %0 : $SwiftClass, let, name "self", argno 1 // id: %1 %2 = metatype $@thick SwiftClass.Type // user: %4 // function_ref SwiftClass.__allocating_init() %3 = function_ref @Simple.SwiftClass.__allocating_init() -> Simple.SwiftClass : $@convention(method) (@thick SwiftClass.Type) -> @owned SwiftClass // user: %4 %4 = apply %3(%2) : $@convention(method) (@thick SwiftClass.Type) -> @owned SwiftClass // users: %14, %13, %10, %11, %8, %9, %6, %7, %5 debug_value %4 : $SwiftClass, let, name "sc" // id: %5 %6 = class_method %4 : $SwiftClass, #SwiftClass.c!1 : (SwiftClass) -> () -> (), $@convention(method) (@guaranteed SwiftClass) -> () // user: %7 %7 = apply %6(%4) : $@convention(method) (@guaranteed SwiftClass) -> () %8 = class_method %4 : $SwiftClass, #SwiftClass.d!1 : (SwiftClass) -> () -> (), $@convention(method) (@guaranteed SwiftClass) -> () // user: %9 %9 = apply %8(%4) : $@convention(method) (@guaranteed SwiftClass) -> () %10 = objc_method %4 : $SwiftClass, #SwiftClass.ce!1.foreign : (SwiftClass) -> () -> (), $@convention(objc_method) (SwiftClass) -> () // user: %11 %11 = apply %10(%4) : $@convention(objc_method) (SwiftClass) -> () // dynamic_function_ref SwiftClass.de() %12 = dynamic_function_ref @Simple.SwiftClass.de() -> () : $@convention(method) (@guaranteed SwiftClass) -> () // user: %13 %13 = apply %12(%4) : $@convention(method) (@guaranteed SwiftClass) -> () strong_release %4 : $SwiftClass // id: %14 %15 = tuple () // user: %16 return %15 : $() // id: %16 } // end sil function 'Simple.SwiftClass.j() -> ()' sil_vtable SwiftClass { #SwiftClass.c!1: (SwiftClass) -> () -> () : @Simple.SwiftClass.c() -> () // SwiftClass.c() #SwiftClass.d!1: (SwiftClass) -> () -> () : @Simple.SwiftClass.d() -> () // SwiftClass.d() #SwiftClass.init!allocator.1: (SwiftClass.Type) -> () -> SwiftClass : @Simple.SwiftClass.__allocating_init() -> Simple.SwiftClass // SwiftClass.__allocating_init() #SwiftClass.deinit!deallocator.1: @Simple.SwiftClass.__deallocating_deinit // SwiftClass.__deallocating_deinit }
@objc修饰之后,OC运行时就可以看到该方法,那就有被替换的风险,为什么主类中的方法还使用虚函数表派发?
我认为是:苹果认为大部分需要暴露给OC的情况,并不是为了动态时替换,而是为了让OC调用或者selector,不能为了一小部分而牺牲了性能,如果需要动态替换,苹果还提供的有dynamic关键字,但是这个解释不了extension中的情况
@objc修饰的 swift 方法如果被替换了,执行原方法,还是替换后的方法?
在swift中调用的话,会执行原方法,在OC中调用的话,会执行替换后的方法
在extension中加上@objc之后,就改为消息转发的原因是,编译器默默地帮我们加上了个dynamic,可是系统为什么要给加上呢?????
dynamic的用法
// dynamic 动态替换 import Foundation class SwiftClass { dynamic func d() { print("d") } } extension SwiftClass { @_dynamicReplacement(for: d()) func e() { print("e") } class func j() { let sc = SwiftClass() sc.d() } }
@objc + dynamic
这种情况,毋庸置疑会使用消息派发,即使再加一个final,也是消息派发
protocol怎么派发的
// protocol怎么派发的 protocol P { func m1() } extension P { func m1() { // print("p") } func m2() { // print("p") } } class A: P { func a() { let a: A = A() let p: P = A() a.m1() // a a.m2() // a p.m1() // a p.m2() // p } func m2() { // print("a") } func m1() { // print("a") } }
protocol是使用 existential container 实现的,这个就是 existential container 的结构
// A.a() sil hidden @Simple.A.a() -> () : $@convention(method) (@guaranteed A) -> () { // %0 // user: %1 bb0(%0 : $A): debug_value %0 : $A, let, name "self", argno 1 // id: %1 %2 = metatype $@thick A.Type // user: %4 // function_ref A.__allocating_init() %3 = function_ref @Simple.A.__allocating_init() -> Simple.A : $@convention(method) (@thick A.Type) -> @owned A // user: %4 %4 = apply %3(%2) : $@convention(method) (@thick A.Type) -> @owned A // users: %24, %14, %15, %12, %13, %5 debug_value %4 : $A, let, name "a" // id: %5 %6 = alloc_stack $P, let, name "p" // users: %23, %22, %19, %16, %10 %7 = metatype $@thick A.Type // user: %9 // function_ref A.__allocating_init() %8 = function_ref @Simple.A.__allocating_init() -> Simple.A : $@convention(method) (@thick A.Type) -> @owned A // user: %9 %9 = apply %8(%7) : $@convention(method) (@thick A.Type) -> @owned A // user: %11 %10 = init_existential_addr %6 : $*P, $A // user: %11 store %9 to %10 : $*A // id: %11 %12 = class_method %4 : $A, #A.m1!1 : (A) -> () -> (), $@convention(method) (@guaranteed A) -> () // user: %13 %13 = apply %12(%4) : $@convention(method) (@guaranteed A) -> () %14 = class_method %4 : $A, #A.m2!1 : (A) -> () -> (), $@convention(method) (@guaranteed A) -> () // user: %15 %15 = apply %14(%4) : $@convention(method) (@guaranteed A) -> () %16 = open_existential_addr immutable_access %6 : $*P to $*@opened("58F972C8-B201-11EA-941F-F0189837A571") P // users: %18, %18, %17 %17 = witness_method $@opened("58F972C8-B201-11EA-941F-F0189837A571") P, #P.m1!1 : <Self where Self : P> (Self) -> () -> (), %16 : $*@opened("58F972C8-B201-11EA-941F-F0189837A571") P : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () // type-defs: %16; user: %18 %18 = apply %17<@opened("58F972C8-B201-11EA-941F-F0189837A571") P>(%16) : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () // type-defs: %16 %19 = open_existential_addr immutable_access %6 : $*P to $*@opened("58F97868-B201-11EA-941F-F0189837A571") P // users: %21, %21 // function_ref P.m2() %20 = function_ref @(extension in Simple):Simple.P.m2() -> () : $@convention(method) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () // user: %21 %21 = apply %20<@opened("58F97868-B201-11EA-941F-F0189837A571") P>(%19) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () // type-defs: %19 destroy_addr %6 : $*P // id: %22 dealloc_stack %6 : $*P // id: %23 strong_release %4 : $A // id: %24 %25 = tuple () // user: %26 return %25 : $() // id: %26 } // end sil function 'Simple.A.a() -> ()' sil_vtable A { #A.a!1: (A) -> () -> () : @Simple.A.a() -> () // A.a() #A.m2!1: (A) -> () -> () : @Simple.A.m2() -> () // A.m2() #A.m1!1: (A) -> () -> () : @Simple.A.m1() -> () // A.m1() #A.init!allocator.1: (A.Type) -> () -> A : @Simple.A.__allocating_init() -> Simple.A // A.__allocating_init() #A.deinit!deallocator.1: @Simple.A.__deallocating_deinit // A.__deallocating_deinit } sil_witness_table hidden A: P module Simple { method #P.m1!1: <Self where Self : P> (Self) -> () -> () : @protocol witness for Simple.P.m1() -> () in conformance Simple.A : Simple.P in Simple // protocol witness for P.m1() in conformance A }