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
}

 

posted @ 2020-06-19 15:52  小Garfield  阅读(556)  评论(0编辑  收藏  举报