Swift进阶(八)汇编分析多态

多态

  • 多态的定义():
    多态(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后面不应该是固定的地址:

image.png

我们在此处call指令打上断点,跟进去看一下:
image1.png

我们发现,这就是fun1()

这里我们不难发现,Swift中多态的实现,是通过一连串的计算找到对应的函数(方法)。那么究竟是怎么计算的呢?我们接着往下看:

image.png

观察上图,会发现这样一条指令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的多态是如何实现的了。本质就是通过动态的获取函数地址来实现多态。而函数的地址是储存在类型信息里面的。

你可能感兴趣的:(Swift进阶(八)汇编分析多态)