静态调用
先看一下值类型
的方法调度:
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, 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
中对应符号的地址进行跳转。但是此时跳转的这个地址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, 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(_:)
%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
修饰了函数,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(_:)
%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: 函数符号)
进行交换:
总结
值类型
的函数调度是直接调用地址,即静态调用。
引用类型
的函数调度是通过V-Table
函数表来调度的,即动态调度。
class
的extension
中的函数调度方式是静态调用。
final
修饰的函数调度方式是静态调用。
@objc
修饰的函数是函数表调度,如果OC
中需要使用class
还必须继承NSObject
。
dynamic
修饰的函数的调度方式是函数表调度,使函数具有动态性。
@objc + dynamic
组合修饰的函数是通过objc_msgSend
调用,即动态消息转发
。