直接上答案:因为表派发允许子类重写父类的方法,并在运行时根据对象的实际类型调用正确的方法实现。
什么是表派发?
首先我们先知道的是,swift当中函数的派发机制主要分为静态派发和动态派发。动态派发又分为表派发和消息派发。
静态派发(直接派发)
静态派发的速度非常快,因为编译器能在编译时定位指令所在的位置(编译时就会绑定方法)。因此函数被调用时候,编译器直接跳转到函数的内存地址来执行操作。所以就会非常快。使用静态派发的主要是所有的值类型,扩展的方法,以及用final,static的修饰的类和属性。(由于stuct和enum是值类型,且不支持继承,所以编译器把方法放在静态派发)
扩展的方法为什么也是静态派发?
这里我们首先要明白扩展的本质:扩展的用途是为现的类型添加新的功能,而不修改原有类型的定义。
·扩展的方法在编译期间就会被静态的绑定到类型本身
·扩展不能参与类型的继承链
·扩展没有动态和多态,不能覆盖原有类型或字类中的方法。
扩展(extension)不会额外增加运行时的存储空间,因为扩展的本质是在编译期对类型的功能进行静态增强,而不是动态地向类型添加新成员(存储属性会改变类型的内存布局,而扩展被设计为一种编译时功能增强机制。如果允许扩展添加存储属性,会导致类型的内存布局不确定,影响已有代码的兼容性)。扩展是对类型的附加行为,扩展方法不参与类的继承体系,因此不能支持动态派发(多态)。
表(table)派送
表派送只要是利用一个表,该表是一组函数指针,成为witness table(虚拟表),实际上就是swift的运行时表,用来查找特定方法的实现。
witness如何工作的呢?
·每个子类都有自己对于此表的拷贝
·对于此类重写的每个方法,此表都有不同的函数指针
·对于子类添加新方法时,这些方法指针将附加到数组的结尾。
·最后编译器在表中查看为方法调用的具体实现
由于编译器必须从表中读取方法实现的内存地址,然后跳转地址,由于要执行两条指令,所以他比静态派发要慢,但比Message要快。使用表派发的主要是类的实例方法和子类可重写的方法。
消息(Message)派发
消息派发是一种动态派发机制,通过运行时的消息传递来调用方法。他不是直接调用方法的内存地址,而是把消息发送给对象,由对象动态解析方法。在swift中,消息派发主要是支持OC的动态特性,例如方法替换等(Method Swizzing)和运行时(runtime)消息解析。主要用于@objc和dynamic的方法。
回到最开始的为什么要使用表派发?主要是因为支持class的继承和方法的重写(多态),因为这些需要根据对象的实际类型来调用方法。静态派发的话,对象的动态类型无法在编译期间确定。