静态调用
先看一下值类型的方法调度:
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() -> ():

可以看到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:

其中的0x100000000是保留的虚拟空间,它永远不会被用到。所以0x100000000 + 0x7818 = 0x100007818就是函数的在Mach-O实际地址。
在上面汇编代码里面可以看到地址后面还跟有符号,这个符号是存在Mach-O:



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<UITouch>, 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<A>(Builtin.Word) -> (Swift.Array<A>, 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 <compiler-generated>
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 <compiler-generated>
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中对应符号的地址进行跳转。但是此时跳转的这个地址0x0000000102475a54并不是printf真正的调用地址。
(lldb) im lookup -a 0x0000000102475a54
Address: TestSwift[0x0000000100009a54] (TestSwift.__TEXT.__stub_helper + 516)
Summary:
可以看到,这个地址位于当前二进制的__TEXT.__stub_helper段中偏移516的地方:

再给这个地址打上断点,继续运行程序:
(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
可以看到通过入栈将x16和x17寄存器的值作为两个参数传递给了dyld_stub_binder。其中一个参数是_dyld_private的地址,另一个是在之前__TEXT.__stub_helper读出来的0x5bb。那这个值究竟代表什么呢?其实很容易就猜想得到。我们要把print这个符号和动态库中的函数地址绑定起来,肯定要知道print在Mach-O中的地址。而这个值就是print在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的结构:

前面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<UITouch>, 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定位到函数表的初始化:

其内部是通过
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<A>(_:)
%3 = function_ref @Swift._allocateUninitializedArray<A>(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
%4 = apply %3<Any>(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
%5 = tuple_extract %4 : $(Array<Any>, Builtin.RawPointer), 0 // users: %24, %21
%6 = tuple_extract %4 : $(Array<Any>, 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<Any>, @guaranteed String, @guaranteed String) -> () // user: %21
%21 = apply %20(%5, %17, %19) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
release_value %19 : $String // id: %22
release_value %17 : $String // id: %23
release_value %5 : $Array<Any> // 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修饰了函数,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。
@objc和dynamic同时修饰的函数:

@objc和dynamic同时修饰的函数是通过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<A>(_:)
%3 = function_ref @Swift._allocateUninitializedArray<A>(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
%4 = apply %3<Any>(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
%5 = tuple_extract %4 : $(Array<Any>, Builtin.RawPointer), 0 // users: %24, %21
%6 = tuple_extract %4 : $(Array<Any>, 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<Any>, @guaranteed String, @guaranteed String) -> () // user: %21
%21 = apply %20(%5, %17, %19) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
release_value %19 : $String // id: %22
release_value %17 : $String // id: %23
release_value %5 : $Array<Any> // 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: 函数符号)进行交换:

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