swift
中方法派发方式
在swift
语言中方法派发方式一共有3种:直接派发、函数表派发、动态派发。
派发方式介绍
1.直接派发
直接派发效率最高,在编译阶段就能确定方法调用位置,然后会直接调用,不需要复杂的寻址确定方法的过程。编译器也会在编译期间做一些优化。
2.函数表派发
每一个对象都会维护一个方法列表,在方法调用时,会找到方法列表相应的方法,然后进行方法调用。相对于直接派发会多出两次读内存和一次寻址的过程,因为首先需要读到方法列表的指针然后跳转到方法列表,然后读列表中的方法,找到对应方法才能跳转到实现,因此效率相对来说会低一些。
3.动态派发
动态派发会在运行时才确定方法的调用,因此也是效率最低的。但它为kvo
和Method Swizzling
提供了基石,可以说非常实用。
使用场景
在我看来,能够由编译器直接确定方法调用就会使用直接派发。比如说结构体由于其不可继承,那么对于一个结构体其方法也是确定的,所以对于结构体就是直接派发。相应的所有值类型,不可继承的类(加final
)、扩展内类和协议方法、加private
的类方法,都是唯一可以确定的所以都使用直接派发。
对于函数表派发:
class ParentClass {
func method1() {}
func method2() {}
}
class ChildClass: ParentClass {
override func method2() {}
func method3() {}
}
复制代码
对应的数据结构为:
而对于动态派发:
class ParentClass {
dynamic func method1() {}
dynamic func method2() {}
}
class ChildClass: ParentClass {
override func method2() {}
dynamic func method3() {}
}
复制代码
对应的数据结构:
oc
为了提高效率会用一个散列表对常用方法进行缓存,这样就解决了每次调用方法都不停查找的尴尬局面。顺便补充一下
OC
里面类的结构:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
}
复制代码
struct class_data_bits_t {
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ...
}
复制代码
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
// ...
}
复制代码
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
// ....
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
// ....
};
复制代码
rw
表示 readwrite
ro
表示 readonly
。从以上结构中不难看出类可以动态添加方法、属性、协议,但是不可以动态添加变量。
所以总结得方法派发方式:
小例子
protocol Run { }
extension Run {
func run() {
print("run")
}
}
class Dog: Run {
func run() {
print("dog run")
}
}
let dog: Run = Dog()
dog.run() //会打印:run
复制代码
因为在协议的扩展内的方法会采用直接派发,dog
被声明为遵循Run
协议,因此在编译时就确定了其调用的是协议扩展内的run
方法。