多态
- 多态的定义():
多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数实现的。
那么Swift中多态又是怎样实现的呢?
- 首先我们要明白
结构体、枚举
是不允许继承的,只有类
才能够被继承。因此,只有类
才存在重写(override)
的行为。 - 既然
结构体、枚举
,不存在重写(override)
的行为,那么在结构体、枚举
内的方法,是静态的,直接调用。由于类
存在`重写(override)行为,则其内部方法的调用时动态的(因为不知道调用的是父类的方法,还是子类的方法)。
下面我们通过断点调试来看一下两者的不同:
① 结构体
struct Circle {
func fun1() {
print("Circle fun1")
}
func fun2() {
print("Circle fun2")
}
}
var circle = Circle()
circle.fun1()
circle.fun2()
断点查看一下汇编代码
image.png
② 类
class Circle {
func fun1() {
print("Circle fun1")
}
func fun2() {
print("Circle fun2")
}
}
var circle = Circle()
circle.fun1()
circle.fun2()
在汇编代码中寻找call
指令,由于是动态调用,所以call
后面不应该是固定的地址:
我们在此处
call
指令打上断点,跟进去看一下:image1.png
我们发现,这就是
fun1()
image.png这里我们不难发现,Swift中多态的实现,是通过一连串的计算找到对应的函数(方法)。那么究竟是怎么计算的呢?我们接着往下看:
观察上图,会发现这样一条指令
movq 0x46a0(%rip), %rax
,结合注释我们知道,0x46a0(%rip)
是circle
这个指针变量的地址值,然后去这个地址所对应存储空间中,取出前8个字节
取出来给%rax
。我们知道
circle
这个指针变量的地址值所对应的存储空间,就是堆空间对象的地址值。那么movq %rax, -0x60(%rbp)
是把堆内存里面的前8个字节
给到-0x60(%rbp)
,然后又给到%rcx
,最后偏移0x50
(callq *0x50(%rcx)
)执行函数。通过之前的知识,我们都知道,
类
对象在堆内存中的前8个字节
存储的是类型信息
,也就是类型信息
所在的内存空间的地址,结合上面的汇编寻址,我们可以断定,类型信息里面存在着类
中函数的相关信息(函数地址)。大致的关系如下图:
image.png
同样的,如果
子类
重写了父类
的函数,那么类型信息
中存储的函数地址,就会变成子类
重写之后的函数地址
,如果没有重写,则存放的是父类
的函数地址
。
class Circle {
func fun1() {
print("Circle fun1")
}
func fun2() {
print("Circle fun2")
}
}
class SubCircle: Circle {
override func fun1() {
print("SubCircle fun1")
}
func fun2() {
print("SubCircle fun2")
}
}
image.png
类型信息所在的内存是在全局区,本文就不做分析,后续会专门讲以几下内存分布
总结:通过上面的分析,想必大家已经清楚了Swift的多态是如何实现的了。本质就是通过动态的获取函数地址来实现多态。而函数的地址是储存在
类型信息
里面的。
网友评论