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
方法。