OC
作为动态语言
,方法调用,是通过消息发送机制
,void objc_msgSend(id self, SEL cmd,…);
,第一个参数是接受消息的对象;第二个是消息本身,方法的名字后面的就是消息中的那些参数。
Swift
是静态语言
,和OC不同,方法的调用有2种方式:静态调用
& 动态调用
。
一、静态调用
静态调用,即直接地址调用
,调用函数指针,这个函数指针在编译
、链接完成
后就已经确定了,存放在代码段
。结构体是值类型,内部并不存放方法,因此直接通过地址直接调用。
struct Teacher{
func teach() { }
}
var t = Teacher()
t.teach()
image.png
断点调试,可以看到是
直接地址调用
。打开Mach-O
,__text段
,就是所谓的代码段,需要执行的汇编指令
都在这里:image.png
那地址后面的符号(重整后
方法名
)地址:String Table首地址 + 偏移量
image.png
命名重整:工程名+类名+函数名
,但符号表
中并不存储字符串,字符串存储在String Table
(字符串表,存放了所有的变量名
和函数名
,以字符串形式存储),然后根据符号表中的偏移值到字符串中查找对应的字符。
当然我们还可以通过终端查看项目符号表:
nm Mach-O
:查看符号表grep
:管道输出xcrun swift-demangle 符号
:还原符号名称
函数重载
c语言
加-
重整,所以无法区分同名函数,即无法方法重载
。
c语言
,函数名字一样,会直接报错。image.png
OC
中同一作用域内不允许相同函数名,严格意义上也不能实现函数重载,如下:
oc代码:
- (void)eat{}
- (void)eat:(NSString *)name{}
//- (void)eat:(int)age{} //报错:Duplicate declaration of method 'eat:'
+ (void)eat{}
Mach-O符号:
- [Animal eat]
- [Animal eat:]
//- [Animal eat:]
+ [Animal eat]
Swift
通过命名重整
技术,使方法名符号复杂
,保证方法符号的唯一
,因此允许方法重载
。
二、动态调用—类的方法
代码:
class Teacher{
func teach1() { }
func teach2() { }
func teach3() { }
func teach4() { }
func teach5() { }
}
SIL代码:
sil_vtable Teacher {
#Teacher.teach1: (Teacher) -> () -> () : @main.Teacher.teach1() -> () // Teacher.teach1()
#Teacher.teach2: (Teacher) -> () -> () : @main.Teacher.teach2() -> () // Teacher.teach2()
#Teacher.teach3: (Teacher) -> () -> () : @main.Teacher.teach3() -> () // Teacher.teach3()
#Teacher.teach4: (Teacher) -> () -> () : @main.Teacher.teach4() -> () // Teacher.teach4()
#Teacher.teach5: (Teacher) -> () -> () : @main.Teacher.teach5() -> () // Teacher.teach5()
#Teacher.init!allocator: (Teacher.Type) -> () -> Teacher : @main.Teacher.__allocating_init() -> main.Teacher // Teacher.__allocating_init()
#Teacher.deinit!deallocator: @main.Teacher.__deallocating_deinit // Teacher.__deallocating_deinit
}
sil_vtable
:类的函数表
。函数表 可以理解为数组,声明在class内部的方法在不加任何关键字修饰的过程中,是连续存放在我们当前的地址空间中的
。
V-Table
源码:
static void initClassVTable(ClassMetadata *self) {
const auto *description = self->getDescription();
// 可以看成是Metadata地址
auto *classWords = reinterpret_cast<void **>(self);
if (description->hasVTable()) {
// 获取vtable的相关信息
auto *vtable = description->getVTableDescriptor();
auto vtableOffset = vtable->getVTableOffset(description);
// 获取方法描述集合
auto descriptors = description->getMethodDescriptors();
// &classWords[vtableOffset]可以看成是V-Table的首地址
// 将方法描述中的方法指针按顺序存储在V-Table中
for (unsigned i = 0, e = vtable->VTableSize; i < e; ++i) {
auto &methodDescription = descriptors[i];
swift_ptrauth_init_code_or_data(
&classWords[vtableOffset + i], methodDescription.getImpl(),
methodDescription.Flags.getExtraDiscriminator(),
!methodDescription.Flags.isAsync());
}
}
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());
// 指向原来(基类)的MethodDescriptor地址
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.
// 基类的MethodDescriptors
auto baseClassMethods = baseClass->getMethodDescriptors();
// If the method descriptor doesn't land within the bounds of the
// method table, abort.
// 如果baseMethod不符合在基类的MethodDescriptors中间,报错
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();
// 基类的vTable地址 + baseMethod在baseClassMethods的index???
auto offset = (baseVTable- >getVTableOffset(baseClass) +
(baseMethod - baseClassMethods.data()));
swift_ptrauth_init_code_or_data(&classWords[offset],
descriptor.getImpl(),
baseMethod->Flags.getExtraDiscriminator(),
!baseMethod->Flags.isAsync());
}
}
}
创建方法主要分成两部分:
- 获取
vtable
信息,获取方法descriptions
,将方法Description
的指针Imp(未重写的)存储在V-Table(元数据地址 + vtableOffset)
中。- 获取
OverrideTable
信息,获取overrideDescriptors
,将description
的指针Imp(重写的)
存储在V-Table(offset )
中,此处的offset为基类的vTable地址 +baseMethod在baseClassMethods的index
。
即、一个类的V-Table
是由自身方法
和重写方法(父类方法)
组成,对比OC
需要去父类去查找,Swift用空间换时间
,提高了查找效率。
关于 extension
,继承
方法和属性,不能写extension
中。而extension
中创建的函数,一定是只属于自己类
,但是其子类
也有其访问权限,只是不能继承和重写
。
扩展的函数并没有在函数表中。新建子类,通过sil一样会发现,子类函数表中只会继承父类函数表中的函数。为什么呢?
因为,子类将父类的函数表全部继承了,此时子类增加函数,那么就继续在连续的地址中insert。如果extension函数也是在函数表中,则意味着子类也有父类extension中声明的函数,但是子类并没有相关的指针记录函数是父类方法
还是子类方法
,所以子类方法不知道该insert到哪里,导致extension中的函数无法安全的放入子类
中.
所以,扩展的方法是直接(静态)调用,并只属于当前类,子类只能使用,无法继承和重写。
验证
调用方法的地方,打上断点,打开汇编:
观察发现,
对象指针
每次偏移8字节
找到方法,如[x8, #0x58]
、[x8, #0x60]
,然后调用。而不是,直接调用方法地址的,参考原文静态调用
部分。
关于汇编,
arm64汇编指令
:
mov
:将某一寄存器
的值复制
到另一寄存器
(只能用于寄存器与寄存器,或者寄存器与常量之间 传值,不能用于内存地址),
例,mov x1, x0
将寄存器x0
的值复制
到寄存器x1
中;ldr
:将内存中的值读取
到寄存器中,
例,ldr x0, [x1, x2]
将寄存器x1
和寄存器x2
相加
作为地址,取该内存地址的值
翻入寄存器x0
中;str
:将寄存器中的值写入
到内存中,
例,str x0, [x0, x8]
将寄存器x0的值 保存
到内存[x0 + x8]
处;blr
:带返回的跳转指令
,跳转到指令后边跟随寄存器中保存的地址;bl
:跳转
到某地址。
三、final、@objc、dynamic修饰函数
final
修饰的方法是 直接调度
;
@objc
关键字是将swift中的方法暴露给OC,函数表
调度;
@objc + NSObject
@objc修饰函数,OC还是无法调用swift方法的,因此如果想要OC访问swift
,class需要继承NSObject;
dynamic
:可以动态修改,意味着当类继承自NSObject
时,可以使用method-swizzling
,走的是objc_msgSend
流程,即 动态消息转发
;
@_dynamicReplacement(for: 函数符号)
进行方法交换。
关于方法的调度,更详细可以参考文章 Swift 底层是怎么调度方法的
method dispatch.png
网友评论