在
Swfit结构体一文中我们介绍了,结构体方法的调度,接下来我们来探索swift中类对象
方法调度。
VTable
SIL分析
首先我们先来看下如下代码
class Person {
var age = 10
func run(){
print(1)
}
func walk(){
print(2)
}
func play(){
print(3)
}
}
var p = Person()
p.run()
p.walk()
p.play()
定义了3个实例方法,我们先来看一下它的SIL
代码长什么样子
swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil && open main.sil
- 将 main.swift 文件,编译为 SIL 文件,并且进行反混淆,并打开 main.sil 文件
class Person {
@_hasStorage @_hasInitialValue var age: Int { get set }
func run()
func walk()
func play()
@objc deinit
init()
}
sil_vtable Person {
#Person.age!getter: (Person) -> () -> Int : @main.Person.age.getter : Swift.Int // Person.age.getter
#Person.age!setter: (Person) -> (Int) -> () : @main.Person.age.setter : Swift.Int // Person.age.setter
#Person.age!modify: (Person) -> () -> () : @main.Person.age.modify : Swift.Int // Person.age.modify
#Person.run: (Person) -> () -> () : @main.Person.run() -> () // Person.run()
#Person.walk: (Person) -> () -> () : @main.Person.walk() -> () // Person.walk()
#Person.play: (Person) -> () -> () : @main.Person.play() -> () // Person.play()
#Person.init!allocator: (Person.Type) -> () -> Person : @main.Person.__allocating_init() -> main.Person // Person.__allocating_init()
#Person.deinit!deallocator: @main.Person.__deallocating_deinit // Person.__deallocating_deinit
}
sil @main : $@convention(c)
...
%12 = class_method %9 : $Person, #Person.run : (Person) -> () -> (), $@convention(method) (@guaranteed Person) -> () // user: %13
%13 = apply %12(%9) : $@convention(method) (@guaranteed Person) -> ()
...
sil_vtable
是关键字,Person
表示当前是Person
的函数表vtable
中存储了所有的实例方法,包括属性的getter
和setter
方法。class_method
: 查找 Person类的 run方法,然后直接调用
从SIL文件,我们可以看出 实例对象的所有方法都存放在vtable中。
源码分析
vtable
是通过initClassVTable
方法来创建的
static void initClassVTable(ClassMetadata *self) {
const auto *description = self->getDescription();
auto *classWords = reinterpret_cast<void **>(self);
if (description->hasVTable()) {
auto *vtable = description->getVTableDescriptor();
auto vtableOffset = vtable->getVTableOffset(description);
auto descriptors = description->getMethodDescriptors();
// 1, 将method 存入内存中
for (unsigned i = 0, e = vtable->VTableSize; i < e; ++i) {
auto &methodDescription = descriptors[i];
swift_ptrauth_init(&classWords[vtableOffset + i],
methodDescription.Impl.get(),
methodDescription.Flags.getExtraDiscriminator());
}
}
if (description->hasOverrideTable()) {
auto *overrideTable = description->getOverrideTable();
auto overrideDescriptors = description->getMethodOverrideDescriptors();
for (unsigned i = 0, e = overrideTable->NumEntries; i < e; ++i) {
auto &descriptor = overrideDescriptors[i];
// Get the base class and method.
auto *baseClass = cast_or_null<ClassDescriptor>(descriptor.Class.get());
auto *baseMethod = descriptor.Method.get();
// If the base method is null, it's an unavailable weak-linked
// symbol.
if (baseClass == nullptr || baseMethod == nullptr)
continue;
// Calculate the base method's vtable offset from the
// base method descriptor. The offset will be relative
// to the base class's vtable start offset.
auto baseClassMethods = baseClass->getMethodDescriptors();
// If the method descriptor doesn't land within the bounds of the
// method table, abort.
if (baseMethod < baseClassMethods.begin() ||
baseMethod >= baseClassMethods.end()) {
fatalError(0, "resilient vtable at %p contains out-of-bounds "
"method descriptor %p\n",
overrideTable, baseMethod);
}
// Install the method override in our vtable.
auto baseVTable = baseClass->getVTableDescriptor();
auto offset = (baseVTable->getVTableOffset(baseClass) +
(baseMethod - baseClassMethods.data()));
swift_ptrauth_init(&classWords[offset],
descriptor.Impl.get(),
baseMethod->Flags.getExtraDiscriminator());
}
}
}
- 1,通过
for循环
,从 i = 0开始,直到VTableSize
,将method依次读取到内存中 ,每个method
的偏移量为vtableOffset + i
,在内存中是连续存放的,是一个数组结构。
extension
我们现在给Person
类,扩展一个方法
class Person {
var age = 10
func run(){
print(1)
}
func walk(){
print(2)
}
func play(){
print(3)
}
}
extension Person {
func study() {
}
}
我们直接使用汇编来查看其调用
我们可以看出,对于
extension
里面的 func
是静态调度
的。
final
使用 final
修饰的方法,也是静态调度方法。
class Person {
var age = 10
final func run(){
print(1)
}
}
var p = Person()
p.run()
其汇编结果为
0x1000035ef <+111>: leaq -0x20(%rbp), %rdi
0x1000035f3 <+115>: movq %rax, -0x50(%rbp)
0x1000035f7 <+119>: callq 0x100003d32 ; symbol stub for: swift_endAccess
0x1000035fc <+124>: movq -0x48(%rbp), %r13
0x100003600 <+128>: callq 0x100003820 ; LYSwift.Person.run() -> () at main.swift:12
@objc
我们先将如下代码转化为SIL
class Person {
@objc func walk(){
}
}
var p = Person()
p.walk()
结果为
class Person {
@objc func walk()
@objc deinit
init()
}
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
......
%12 = class_method %9 : $Person, #Person.walk : (Person) -> () -> (), $@convention(method) (@guaranteed Person) -> () // user: %13
%13 = apply %12(%9) : $@convention(method) (@guaranteed Person) -> ()
strong_release %9 : $Person // id: %14
....
return %16 : $Int32 // id: %17
}
// Person.walk()
sil hidden @main.Person.walk() -> () : $@convention(method) (@guaranteed Person) -> () {
// %0 "self" // user: %1
bb0(%0 : $Person):
debug_value %0 : $Person, let, name "self", argno 1 // id: %1
%2 = tuple () // user: %3
return %2 : $() // id: %3
} // end sil function 'main.Person.walk() -> ()'
// @objc Person.walk()
sil hidden [thunk] @@objc main.Person.walk() -> () : $@convention(objc_method) (Person) -> () {
// %0 // users: %4, %3, %1
bb0(%0 : $Person):
strong_retain %0 : $Person // id: %1
// function_ref Person.walk()
%2 = function_ref @main.Person.walk() -> () : $@convention(method) (@guaranteed Person) -> () // user: %3
%3 = apply %2(%0) : $@convention(method) (@guaranteed Person) -> () // user: %5
strong_release %0 : $Person // id: %4
return %3 : $() // id: %5
} // end sil function '@objc main.Person.walk() -> ()'
sil_vtable Person {
#Person.walk: (Person) -> () -> () : @main.Person.walk() -> () // Person.walk()
#Person.init!allocator: (Person.Type) -> () -> Person : @main.Person.__allocating_init() -> main.Person // Person.__allocating_init()
#Person.deinit!deallocator: @main.Person.__deallocating_deinit // Person.__deallocating_deinit
}
- 1, 分别生成了两个函数
Person.walk()
和@objc Person.walk()
。 - 2,
@objc Person.walk()
中,实际调用Person.walk()
。
@objc
关键字只是通过编译器给OC层提供一个函数调用的入口,标记暴露给OC,它修饰的方法不是静态调度的。如果想要在 OC 调用该方法,需要继承NSObject
dynamic
dynamic
修饰的func
,该方法就变成了一个动态方法,可以进行方法交换
。
class Person {
dynamic func walk(){
print("walk")
}
}
extension Person {
// 用 study 替换 walk
@_dynamicReplacement(for: walk)
func study() {
print("study")
}
}
var p = Person()
p.walk() // study
- 被替换的方法必须使用
dynamic
修饰。 _dynamicReplacement(for:methodA)
使用当前方法动态替换methodA
。
如果我们使用 @objc + dynamic
修饰函数,那么该方法就变成了消息发送
class Person {
@objc dynamic func walk(){
print("walk")
}
}
var p = Person()
p.walk()
通过其汇编代码我们可以验证
0x100003bdc <+124>: movq 0x450d(%rip), %rsi ; "walk"
0x100003be3 <+131>: movq -0x48(%rbp), %rax
0x100003be7 <+135>: movq %rax, %rdi
0x100003bea <+138>: callq 0x100003e28 ; symbol stub for: objc_msgSend
总结
swift类
中方法存储在VTable
中,在编译期间就会确定,extension
中的方法不存放在VTable
中,属于静态调用方法。final
修饰的方法也是静态调用,@objc dynamic
修饰的方法,调用时会进行消息转发
。