Swift方法调度

静态调用

先看一下值类型的方法调度:

struct Person {
    var age: UInt = 26
    var name: String = "dotry"
    
    mutating func celebrateBirthday() {
        age += 1
    }
}
var p = Person()
p.celebrateBirthday()

通过lldb反汇编指令disassemble或者简写di查看当前函数的汇编代码。

(lldb) di
TestSwift`main:
    ......
   -> 0x1023f3654 <+104>: bl     0x1023f3818               ; 
    TestSwift.Person.celebrateBirthday() -> () at main.swift:22
    ......

可以看到执行p.celebrateBirthday()的对应汇编指令是bl加地址0x1023f3818,从而证明值类型的方法通过静态派发调用的。这个函数指针在编译、链接完成后就已经确定了,存放在代码段。结构体内部并不存放方法,通过地址直接调用。

打开项目的Mach-O文件,查看__text代码段,找到TestSwift.Person.celebrateBirthday() -> ():

Person.celebrateBirthday方法Mach-O中__text段的位置

可以看到Mach-O中的地址和汇编指令跳转的地址并不相同,原因是iOS使用了ASLR(地址空间布局随机化 address space layout randomizes)。
通过lldb指令image list查看文件镜像,找到第一个,即主程序Mach-O

(lldb) image list
[  0] FE744083-BBD6-3559-8540-94FA72E32DCA 0x00000001023ec000 /Users/miaokii/Library/Developer/Xcode/DerivedData/TestSwift-axmyavmtpkisingjynjxrpomprkt/Build/Products/Debug-iphoneos/TestSwift.app/TestSwift 

其中的0x00000001023ec000就是程序的起始地址。用函数虚拟地址0x1023f3818减去起始地址0x00000001023ec000得到0x7818就是函数实际在Mach-O__TEXT偏移量

那为何实际地址会比偏移量大0x100000000呢?
再在Mach-O中找到LC_SEGMENT_64__PAGEZERO:

Mach-O__PAGEZERO

其中的0x100000000保留的虚拟空间,它永远不会被用到。所以0x100000000 + 0x7818 = 0x100007818就是函数的在Mach-O实际地址。

在上面汇编代码里面可以看到地址后面还跟有符号,这个符号是存在Mach-O

Mach-O中的符号

String Table首地址

String Table表中的符号

Symbals表不存符号,而是存的符号在String Table表中的偏移量,通过偏移量去读取符号。

还可以通过在命令行用nm指令查找符号:

nm TestSwift | grep "celebrate"
0000000100007818 T _$s9TestSwift6PersonV17celebrateBirthdayyyF

当我们项目打包上线才去的是release模式,在这种模式下会进行符号剥离节省API包的体积。因为静态链接是不需要符号的,当编译完成地址就已经确定了,然后删除对应符号。debug模式下保留符号是为了方便我们调试程序,容易定位到具体位置。

当然release模式也不会删除所有的符号,比如系统的动态库在编译的时候根本不知道地址,所以需要保留。对于不能确定地址的符号,是在运行时确定的,即函数第一次调用时,相当于懒加载。例如print,是通过dyld_stub_binder确定地址的。

dyld_stub_binder的定义
当程序进行编译时,编译器无法确定系统库或者App自身的动态库中定义的符号调用地址的,因为依赖的动态库是在运行环境中动态加载的。 在编译过程中,外部定义的方法会在Mach-O中进行记录,这些需要进行延迟绑定的符号都被记录在Mach-O中的Dynamic Loader Info -> Lazy Binding Info中。

写一个demo,调用两次print

 override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        print("1")
        print("2")
    }

给第一次调用的地方打上断点,进入断点后查看汇编:

(lldb)
TestSwift`ViewController.touchesBegan(_:with:):
    0x102471eb4 <+0>:   sub    sp, sp, #0xb0
    ......
    0x102471f78 <+196>: bl     0x102475688               ; symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
    0x102471f7c <+200>: ldr    x0, [sp, #0x38]
    0x102471f80 <+204>: bl     0x1024757f0               ; symbol stub for: swift_bridgeObjectRelease
    0x102471f84 <+208>: ldr    x0, [sp, #0x48]
    0x102471f88 <+212>: bl     0x1024757f0               ; symbol stub for: swift_bridgeObjectRelease
    0x102471f8c <+216>: ldur   x0, [x29, #-0x30]
    0x102471f90 <+220>: bl     0x1024757f0               ; symbol stub for: swift_bridgeObjectRelease
    0x102471f94 <+224>: ldur   x0, [x29, #-0x20]
    0x102471f98 <+228>: ldur   x1, [x29, #-0x28]
    0x102471f9c <+232>: bl     0x102475670               ; symbol stub for: Swift._allocateUninitializedArray(Builtin.Word) -> (Swift.Array, Builtin.RawPointer)
    0x102471fa0 <+236>: adrp   x8, 5
    0x102471fa4 <+240>: add    x8, x8, #0x7e2            ; =0x7e2 
    0x102471fa8 <+244>: str    x0, [sp, #0x30]
    0x102471fac <+248>: mov    x0, x8
    0x102471fb0 <+252>: ldur   x2, [x29, #-0x20]
    0x102471fb4 <+256>: str    x1, [sp, #0x28]
    0x102471fb8 <+260>: mov    x1, x2
    0x102471fbc <+264>: ldur   w9, [x29, #-0x3c]
    0x102471fc0 <+268>: and    w2, w9, #0x1
    0x102471fc4 <+272>: bl     0x1024755e0               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x102471fc8 <+276>: ldur   x8, [x29, #-0x48]
    0x102471fcc <+280>: ldr    x10, [sp, #0x28]
    0x102471fd0 <+284>: str    x8, [x10, #0x18]
    0x102471fd4 <+288>: str    x0, [x10]
    0x102471fd8 <+292>: str    x1, [x10, #0x8]
    0x102471fdc <+296>: bl     0x102472038               ; default argument 1 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () at 
    0x102471fe0 <+300>: str    x0, [sp, #0x20]
    0x102471fe4 <+304>: str    x1, [sp, #0x18]
    0x102471fe8 <+308>: bl     0x102472064               ; default argument 2 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () at 
    0x102471fec <+312>: ldr    x3, [sp, #0x30]
    0x102471ff0 <+316>: str    x0, [sp, #0x10]
    0x102471ff4 <+320>: mov    x0, x3
    0x102471ff8 <+324>: ldr    x4, [sp, #0x20]
    0x102471ffc <+328>: str    x1, [sp, #0x8]
    0x102472000 <+332>: mov    x1, x4
    0x102472004 <+336>: ldr    x2, [sp, #0x18]
    0x102472008 <+340>: ldr    x3, [sp, #0x10]
    0x10247200c <+344>: ldr    x4, [sp, #0x8]
    0x102472010 <+348>: bl     0x102475688               ; symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
    ......

可以看到两次都是调用的0x102475688这个函数地址,即print桩函数 symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()

再查看当前程序的起始地址,拿到ASLR

im list
[  0] 41695B92-D28A-3E12-BFA5-848B27513465 0x000000010246c000 /Users/miaokii/Library/Developer/Xcode/DerivedData/TestSwift-axmyavmtpkisingjynjxrpomprkt/Build/Products/Debug-iphoneos/TestSwift.app/TestSwift

所以ASLR的值是0x246c000

接下来给这个桩函数打上断点,继续执行进入到断点里面:

(lldb) b -a 0x102475688
Breakpoint 4: where = TestSwift`symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> (), address = 0x0000000102475688
TestSwift`Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ():
    // 空操作
->  0x102475688 <+0>: nop    
    // 当前pc寄存器的值加上0x6a04赋值给x16寄存器。
    // x16 = 0x10247568c + 0x6a04 = 0x000000010247c090
    0x10247568c <+4>: ldr    x16, #0x6a04              ; (void *)0x0000000102475a54
    // 跳转到x16寄存器存的地址
    0x102475690 <+8>: br     x16
(lldb) p/x0x10247568c + 0x6a04
(long) $1 = 0x000000010247c090
(lldb) im lookup -a 0x000000010247c090
      Address: TestSwift[0x0000000100010090] (TestSwift.__DATA.__la_symbol_ptr + 144)
      Summary: (void *)0x0000000102475a54

可以看到,0x000000010247c090这个地址存储的数据是0x0000000102475a54,这跟第二行汇编后面Xcode解析出来的调用地址是一样的。而0x000000010247c090这个地址存在于二进制中的__DATA.__la_symbol_ptr段中偏移量为144的地方,首地址0x10000 + 144 = 0x10090。data的值0x1000009a54+ASLR0x246c000 = 0x0000001002475a54

__DATA.__la_symbol_ptr

通过以上就可以知道桩函数的作用了,它读取__DATA.__la_symbol_ptr中对应符号的地址进行跳转。但是此时跳转的这个地址0x0000000102475a54并不是printf真正的调用地址。

(lldb) im lookup -a 0x0000000102475a54
      Address: TestSwift[0x0000000100009a54] (TestSwift.__TEXT.__stub_helper + 516)
      Summary: 

可以看到,这个地址位于当前二进制的__TEXT.__stub_helper段中偏移516的地方:

__TEXT.__stub_helper

再给这个地址打上断点,继续运行程序:

(lldb) b -a 0x0000000102475a54
Breakpoint 5: where = TestSwift`TestSwift[0x0000000100009a54], address = 0x0000000102475a54

进入断点:

    // 将0x102475a5c存储到w16寄存器
->  0x102475a54: ldr    w16, 0x102475a5c
    // 跳转0x102475850
    0x102475a58: b      0x102475850
    // 将0x5bb存储到w16寄存器
    0x102475a5c: udf    #0x5bb
    //
    0x102475a60: ldr    w16, 0x102475a68
    //
    0x102475a64: b      0x102475850
    //
    0x102475a68: udf    #0x872
    //
    0x102475a6c: ldr    w16, 0x102475a74
    //
    0x102475a70: b      0x102475850
(lldb) im lookup -a 0x102475850
      Address: TestSwift[0x0000000100009850] (TestSwift.__TEXT.__stub_helper + 0)
      Summary:

这里跳转了0x102475850这个地址,通过w16寄存器将参数0x5bb传递了过去。这个值就是刚才在__TEXT.__stub_helper中读取出来的。

0x102475850打上断点,继续执行程序:

(lldb) b -a 0x102475850
Breakpoint 6: where = TestSwift`TestSwift[0x0000000100009850], address = 0x0000000102475850

进入断点

    // 拿到_dyld_private的地址存到寄存器x17
    0x100915850: adr    x17, #0x8418              ; _dyld_private
    0x100915854: nop    
    // 将x16和x17寄存器的值入栈保存
    0x100915858: stp    x16, x17, [sp, #-0x10]!
    0x10091585c: nop    
    // 拿到dyld_stub_binder的地址存到寄存器x16
    0x100915860: ldr    x16, #0x27d8              ; (void *)0x00000001a4f7435c: dyld_stub_binder
    // 跳转到dyld_stub_binder
    0x100915864: br     x16
    0x100915868: ldr    w16, 0x100915870
    0x10091586c: b      0x100915850
(lldb) p/x 0x100915850 + 0x8418
(long) $2 = 0x000000010091dc68
(lldb) im lookup -a 0x00000001043d1c68
      Address: TestSwift[0x0000000100011c68] (TestSwift.__DATA.__data + 0)
      Summary: _dyld_private
(lldb) p/x 0x27d8 + 0x100915860
(long) $5 = 0x0000000100918038
(lldb) im lookup -a 0x0000000100918038
      Address: TestSwift[0x000000010000c038] (TestSwift.__DATA_CONST.__got + 56)
      Summary: (void *)0x00000001a4f7435c: dyld_stub_binder

可以看到通过入栈将x16x17寄存器的值作为两个参数传递给了dyld_stub_binder。其中一个参数是_dyld_private的地址,另一个是在之前__TEXT.__stub_helper读出来的0x5bb。那这个值究竟代表什么呢?其实很容易就猜想得到。我们要把print这个符号和动态库中的函数地址绑定起来,肯定要知道printMach-O中的地址。而这个值就是printDynamic Loader Info -> Lazy Binding info -> Actions中的偏移量:

Dynamic Loader Info -> Lazy Binding info -> Actions

然后dyld_stub_binder就会给符号绑定上在动态库中的地址,当第二次执行print进入桩函数里面,就调用的绑定好的地址:

TestSwift`Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ():
->  0x102475688 <+0>: nop    
    0x10247568c <+4>: ldr    x16, #0x6a04 ; (void *)0x00000001b26f3034: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
    0x102475690 <+8>: br     x16

我们看到一共调用了两次__TEXT.__stub_helper,一次是桩函数里面调用,一次是在dyld_stub_binder之前。那么这两次分别有什么不同呢?
我们再看一下Mach-O__TEXT.__stub_helper的结构:

__TEXT.__stub_helper

前面6行是通用绑定方法,后面每3行一组,其作用是当做一个中间的跳板。每个符号的la_symbol_ptr在初始化时都指向一个这样的跳板。这个跳板就是提供对应符号的信息(例如本例中通过提供0x5bb这个偏移给dyld_stub_binder提供符号名、动态库名信息),每个跳板最终还是会调用通用绑定方法来实现最终的绑定。

至此,就可以总结出整个外部符号绑定的过程:
1.先调用符号的桩函数。
2.桩函数会在.__DATA.__la_symbol_ptr中读取符号的地址。
3.没有绑定的符号地址会指向__TEXT.__stub_helper的跳板,在这里将符号在Dynamic Loader Info -> Lazy Binding info -> Actions的偏移量读取出来。
4.调用通用绑定方法将_dyld_private和符号偏移量传递给dyld_stub_binder`完成绑定。

函数表调用

创建一个类,调用它的函数:

override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        var s = Student()
        s.study1()
        s.study2()
        s.study3()
        s.study4()
        s.study5()
    }
连续的函数地址

可以看到这几个函数的地址是连续的,我们通过SIL来查看一下内部实现。

sil_vtable Student {
  #Student.study1: (Student) -> () -> () : @main.Student.study1() -> () // Student.study1()
  #Student.study2: (Student) -> () -> () : @main.Student.study2() -> () // Student.study2()
  #Student.study3: (Student) -> () -> () : @main.Student.study3() -> () // Student.study3()
  #Student.study4: (Student) -> () -> () : @main.Student.study4() -> () // Student.study4()
  #Student.study5: (Student) -> () -> () : @main.Student.study5() -> () // Student.study5()
  #Student.init!allocator: (Student.Type) -> () -> Student : @main.Student.__allocating_init() -> main.Student  // Student.__allocating_init()
  #Student.deinit!deallocator: @main.Student.__deallocating_deinit  // Student.__deallocating_deinit
}

可以看到SIL生成了一个sil_vtable的东西,里面都是Student的方法,这就是函数表。表中的方法的顺序和刚才调用的顺序是一样的。

在源码查找initClassVTable定位到函数表的初始化:

initClassVTable

其内部是通过for循环编码,通过offset+index偏移获取method。然后将其存入到偏移后的内存中,从这里可以印证函数是连续存放的。所以Swift里不加修饰的方法就是函数表调用。

接下来看一下extension里面的方法是怎样调用的:

class Student {
  func study1()
  func study2()
  @objc deinit
  init()
}

extension Student {
  func study3()
}

sil_vtable Student {
  #Student.study1: (Student) -> () -> () : @main.Student.study1() -> () // Student.study1()
  #Student.study2: (Student) -> () -> () : @main.Student.study2() -> () // Student.study2()
  #Student.init!allocator: (Student.Type) -> () -> Student : @main.Student.__allocating_init() -> main.Student  // Student.__allocating_init()
  #Student.deinit!deallocator: @main.Student.__deallocating_deinit  // Student.__deallocating_deinit
}

可以看到study3并不在sil_vtable里面,这也很好理解。比如你给UIViewController写一个extension,在里面添加一些方法。由于UIViewController是在系统动态库里面的,不可能将添加的方法写进动态库中的。

再看一下关于继承的情况:

class Student {
    func study1() {print("1")}
    func study2() {print("2")}
}
extension Student {
    func study3() {print("study3")}
}
class MidStudent: Student {
    func study4() {print("study4")}
}
class Student {
  func study1()
  func study2()
  @objc deinit
  init()
}

extension Student {
  func study3()
}

@_inheritsConvenienceInitializers class MidStudent : Student {
  func study4()
  @objc deinit
  override init()
}

sil_vtable Student {
  #Student.study1: (Student) -> () -> () : @main.Student.study1() -> () // Student.study1()
  #Student.study2: (Student) -> () -> () : @main.Student.study2() -> () // Student.study2()
  #Student.init!allocator: (Student.Type) -> () -> Student : @main.Student.__allocating_init() -> main.Student  // Student.__allocating_init()
  #Student.deinit!deallocator: @main.Student.__deallocating_deinit  // Student.__deallocating_deinit
}

sil_vtable MidStudent {
  #Student.study1: (Student) -> () -> () : @main.Student.study1() -> () [inherited] // Student.study1()
  #Student.study2: (Student) -> () -> () : @main.Student.study2() -> () [inherited] // Student.study2()
  #Student.init!allocator: (Student.Type) -> () -> Student : @main.MidStudent.__allocating_init() -> main.MidStudent [override] // MidStudent.__allocating_init()
  #MidStudent.study4: (MidStudent) -> () -> () : @main.MidStudent.study4() -> ()    // MidStudent.study4()
  #MidStudent.deinit!deallocator: @main.MidStudent.__deallocating_deinit    // MidStudent.__deallocating_deinit
}

子类也将父类的方法继承写进了函数表,并且父类extension中的方法子类没有继承。这也说明了为什么父类中extension的方法有权访问却不能重写:

class MidStudent: Student {
    func study4() {print("study4")}
    override func study3() {} ⚠️ Overriding non-@objc declarations from extensions is not supported
}

final修饰的函数:

class Student {
    func study1() {print("1")}
    func study2() {print("2")}
    final func finalStudy() {print("finalStudy")}
}
var s = Student()
s.finalStudy()
    0x102eb3660 <+128>: ldr    x20, [sp, #0x8]
    0x102eb3664 <+132>: bl     0x102eb38a0               ; TestSwift.Student.finalStudy() -> () at main.swift:39
    0x102eb3668 <+136>: ldr    x0, [sp, #0x8]
    0x102eb366c <+140>: bl     0x102eb5758               ; symbol stub for: swift_release
    0x102eb3670 <+144>: mov    w10, #0x0
    0x102eb3674 <+148>: mov    x0, x10
    0x102eb3678 <+152>: ldp    x29, x30, [sp, #0x50]
    0x102eb367c <+156>: ldp    x20, x19, [sp, #0x40]
    0x102eb3680 <+160>: add    sp, sp, #0x60             ; =0x60 
    0x102eb3684 <+164>: ret    

通过汇编看到final修饰的函数是静态调用的。

@objc修饰的函数

class Student {
    func study1() {print("1")}
    func study2() {print("2")}
    final func finalStudy() {print("finalStudy")}
    @objc func objcStudy() {print("objcStudy")}
}
// Student.objcStudy()
sil hidden @main.Student.objcStudy() -> () : $@convention(method) (@guaranteed Student) -> () {
// %0 "self"                                      // user: %1
bb0(%0 : $Student):
  debug_value %0 : $Student, let, name "self", argno 1 // id: %1
  %2 = integer_literal $Builtin.Word, 1           // user: %4
  // function_ref _allocateUninitializedArray(_:)
  %3 = function_ref @Swift._allocateUninitializedArray(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
  %4 = apply %3(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
  %5 = tuple_extract %4 : $(Array, Builtin.RawPointer), 0 // users: %24, %21
  %6 = tuple_extract %4 : $(Array, Builtin.RawPointer), 1 // user: %7
  %7 = pointer_to_address %6 : $Builtin.RawPointer to [strict] $*Any // user: %14
  %8 = string_literal utf8 "objcStudy"            // user: %13
  %9 = integer_literal $Builtin.Word, 9           // user: %13
  %10 = integer_literal $Builtin.Int1, -1         // user: %13
  %11 = metatype $@thin String.Type               // user: %13
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %12 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %13
  %13 = apply %12(%8, %9, %10, %11) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
  %14 = init_existential_addr %7 : $*Any, $String // user: %15
  store %13 to %14 : $*String                     // id: %15
  // function_ref default argument 1 of print(_:separator:terminator:)
  %16 = function_ref @default argument 1 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () : $@convention(thin) () -> @owned String // user: %17
  %17 = apply %16() : $@convention(thin) () -> @owned String // users: %23, %21
  // function_ref default argument 2 of print(_:separator:terminator:)
  %18 = function_ref @default argument 2 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () : $@convention(thin) () -> @owned String // user: %19
  %19 = apply %18() : $@convention(thin) () -> @owned String // users: %22, %21
  // function_ref print(_:separator:terminator:)
  %20 = function_ref @Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () : $@convention(thin) (@guaranteed Array, @guaranteed String, @guaranteed String) -> () // user: %21
  %21 = apply %20(%5, %17, %19) : $@convention(thin) (@guaranteed Array, @guaranteed String, @guaranteed String) -> ()
  release_value %19 : $String                     // id: %22
  release_value %17 : $String                     // id: %23
  release_value %5 : $Array                  // id: %24
  %25 = tuple ()                                  // user: %26
  return %25 : $()                                // id: %26
} // end sil function 'main.Student.objcStudy() -> ()'

// @objc Student.objcStudy()
sil hidden [thunk] @@objc main.Student.objcStudy() -> () : $@convention(objc_method) (Student) -> () {
// %0                                             // users: %4, %3, %1
bb0(%0 : $Student):
  strong_retain %0 : $Student                     // id: %1
  // function_ref Student.objcStudy()
  %2 = function_ref @main.Student.objcStudy() -> () : $@convention(method) (@guaranteed Student) -> () // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed Student) -> () // user: %5
  strong_release %0 : $Student                    // id: %4
  return %3 : $()                                 // id: %5
} // end sil function '@objc main.Student.objcStudy() -> ()'

sil_vtable Student {
  #Student.study1: (Student) -> () -> () : @main.Student.study1() -> () // Student.study1()
  #Student.study2: (Student) -> () -> () : @main.Student.study2() -> () // Student.study2()
  #Student.objcStudy: (Student) -> () -> () : @main.Student.objcStudy() -> ()   // Student.objcStudy()
  #Student.init!allocator: (Student.Type) -> () -> Student : @main.Student.__allocating_init() -> main.Student  // Student.__allocating_init()
  #Student.deinit!deallocator: @main.Student.__deallocating_deinit  // Student.__deallocating_deinit
}

调用@objc修饰的函数

很明显@objc修饰的函数也是通过函数表调用的,不过生成了两个方法。一个有@objc修饰,一个没有,在有修饰符的那个函数里面又调用了没有修饰的函数。
注意即使用@objc修饰了函数,OC依旧无法调用修饰的函数,还需要将类继承自NSObject

dynamic修饰的函数

sil_vtable Student {
  #Student.study1: (Student) -> () -> () : @main.Student.study1() -> () // Student.study1()
  #Student.study2: (Student) -> () -> () : @main.Student.study2() -> () // Student.study2()
  #Student.objcStudy: (Student) -> () -> () : @main.Student.objcStudy() -> ()   // Student.objcStudy()
  #Student.dynamicStudy: (Student) -> () -> () : @main.Student.dynamicStudy() -> () // Student.dynamicStudy()
  #Student.init!allocator: (Student.Type) -> () -> Student : @main.Student.__allocating_init() -> main.Student  // Student.__allocating_init()
  #Student.deinit!deallocator: @main.Student.__deallocating_deinit  // Student.__deallocating_deinit
}

dynamic修饰的函数还是通过函数表调用的,不过对比@objc并没有生成两个方法。使用dynamic的意思是可以动态修改,意味着当类继承自NSObject时,可以使用method-swizzling

@objcdynamic同时修饰的函数

调用@objc和dynamic同时修饰的函数

@objcdynamic同时修饰的函数是通过objc_msgSend调用的,即动态消息转发
由于有@objc修饰,最终也会生成两个方法。

// Student.objcDynamicStudy()
sil hidden @main.Student.objcDynamicStudy() -> () : $@convention(method) (@guaranteed Student) -> () {
// %0 "self"                                      // user: %1
bb0(%0 : $Student):
  debug_value %0 : $Student, let, name "self", argno 1 // id: %1
  %2 = integer_literal $Builtin.Word, 1           // user: %4
  // function_ref _allocateUninitializedArray(_:)
  %3 = function_ref @Swift._allocateUninitializedArray(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
  %4 = apply %3(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
  %5 = tuple_extract %4 : $(Array, Builtin.RawPointer), 0 // users: %24, %21
  %6 = tuple_extract %4 : $(Array, Builtin.RawPointer), 1 // user: %7
  %7 = pointer_to_address %6 : $Builtin.RawPointer to [strict] $*Any // user: %14
  %8 = string_literal utf8 "objcDynamicStudy"     // user: %13
  %9 = integer_literal $Builtin.Word, 16          // user: %13
  %10 = integer_literal $Builtin.Int1, -1         // user: %13
  %11 = metatype $@thin String.Type               // user: %13
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %12 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %13
  %13 = apply %12(%8, %9, %10, %11) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
  %14 = init_existential_addr %7 : $*Any, $String // user: %15
  store %13 to %14 : $*String                     // id: %15
  // function_ref default argument 1 of print(_:separator:terminator:)
  %16 = function_ref @default argument 1 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () : $@convention(thin) () -> @owned String // user: %17
  %17 = apply %16() : $@convention(thin) () -> @owned String // users: %23, %21
  // function_ref default argument 2 of print(_:separator:terminator:)
  %18 = function_ref @default argument 2 of Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () : $@convention(thin) () -> @owned String // user: %19
  %19 = apply %18() : $@convention(thin) () -> @owned String // users: %22, %21
  // function_ref print(_:separator:terminator:)
  %20 = function_ref @Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> () : $@convention(thin) (@guaranteed Array, @guaranteed String, @guaranteed String) -> () // user: %21
  %21 = apply %20(%5, %17, %19) : $@convention(thin) (@guaranteed Array, @guaranteed String, @guaranteed String) -> ()
  release_value %19 : $String                     // id: %22
  release_value %17 : $String                     // id: %23
  release_value %5 : $Array                  // id: %24
  %25 = tuple ()                                  // user: %26
  return %25 : $()                                // id: %26
} // end sil function 'main.Student.objcDynamicStudy() -> ()'

// @objc Student.objcDynamicStudy()
sil hidden [thunk] @@objc main.Student.objcDynamicStudy() -> () : $@convention(objc_method) (Student) -> () {
// %0                                             // users: %4, %3, %1
bb0(%0 : $Student):
  strong_retain %0 : $Student                     // id: %1
  // function_ref Student.objcDynamicStudy()
  %2 = function_ref @main.Student.objcDynamicStudy() -> () : $@convention(method) (@guaranteed Student) -> () // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed Student) -> () // user: %5
  strong_release %0 : $Student                    // id: %4
  return %3 : $()                                 // id: %5
} // end sil function '@objc main.Student.objcDynamicStudy() -> ()'

Swift中实现方法交换
使用dynamic修饰swift中的需要交换的函数,然后通过@_dynamicReplacement(for: 函数符号)进行交换:

Swift的方法交换

总结
值类型的函数调度是直接调用地址,即静态调用。
引用类型的函数调度是通过V-Table函数表来调度的,即动态调度。
classextension中的函数调度方式是静态调用。
final修饰的函数调度方式是静态调用。
@objc修饰的函数是函数表调度,如果OC中需要使用class还必须继承NSObject
dynamic修饰的函数的调度方式是函数表调度,使函数具有动态性。
@objc + dynamic 组合修饰的函数是通过objc_msgSend调用,即动态消息转发

你可能感兴趣的:(Swift方法调度)