Dispatch Method的背景知識,已經有點跳脫寫程式的範疇,所以改以機器的角度來思考,會比較好理解。開始之前,我們先記著一個原則:函式 (function) 會被記在記憶體中,同時會被派發一個位址(Address或指針 Pointer),我們稱為「函式位址」,而派發位址來呼叫函式的方法,就叫做Dispatch Method。其中分為靜態派發(Static)與動態派發(Dynamic)。
- 靜態:直接給定函式位址
- 動態:透過其他資訊取得函式位址
內容目錄
比喻
今天小S跟大D兩位工程師,收到Matchbox總公司的Email,指派要到「同一間子公司的同一個部門」出差開會,而他們的 Email內容包含的資訊有,小S:子公司名稱、子公司地址、部門樓層位置。大D:只收到子公司的名稱與部門名稱。請問誰最能輕鬆抵達會議現場?顯然是小S。大D要先查到子公司的地址、再問接待人員部門在哪個樓層。
靜態派發,就類似小S收到的Email內容,CPU會在compile-time就提供精準的函式位址。而動態派發,則需要在runtime時,透過額外的流程,才能拿到函式位址。由此可知 => Static 效率 > Dynamic 效率
Dynamic Dispatch
動態派發一般只發生在Reference Type(如 Class
),主要原因是「Inheritance 繼承」。Value Types並不支持繼承。另外還有在實現「Polymorphism 多型」時,也會透過動態派發。動態派發有以下兩種類型:
Table Dispatch
此類型會運用一個Table (witness table or virtual table),代表著某個Class的函式位址陣列。每個Class會有自己的一個表,如果是繼承父類的Subclass,也會包含Overridden與自己的functions。
function 1 0x121 | function 2 0x222 | function 3 0x322 |
overridden function 1 | function 2 | function 3 |
0xC00
Compiler在Runtime時,會先讀取特定類的表以取得函式位址,再跳轉呼叫函式。因此額外執行了兩次讀與跳轉,加上執行在runtime,編譯器無法對其進行優化,這也是效率會比靜態來得差的原因。
要呼叫function 2的整個流程就是:讀取物件 0xC00
的函式表 -> 讀取取得 function 2的位址 (0x222
) -> 跳轉至 0x222
執行
Message Dispatch
Message也應用了Table,但會以Tree的方式來呈現不同層級關係。
Swift中的派發方式
Initial Declaration | Extension | |
Value Type (Struct) | Static | Static |
Protocol | Table | Static |
Class | Table | Static |
NSObject Subclass | Table | Message |
final | -> Static |
dynamic | -> Message |
@objc | -> Message (Modify Objective-C Visibility) |
@inline | -> Code generation hint for direct dispatch |
範例
// 1. Value Type (Struct): All Static Dispatch
struct Person {
func isHungry() -> Bool { } // Static
}
extension Person {
func sayHello() -> String { } // Static
}
// 2. Protocol: Table & Static
protocol Animal {
func isCute() -> Bool { } // Table
}
extension Animal {
func canGetAngry() -> Bool { } // Static
}
// 3. Class
class Dog: Animal {
func isCute() -> Bool { } // Table
// add @objc & dynamic keyword
@objc dynamic func hoursSleep() -> Int { } // Table -> Message
}
extension Dog {
func canBite() -> Bool { } // Static
// add @objc keyword
@objc func goWild() { } // Static -> Message
}
// add final keyword
final class Employee {
func canCode() -> Bool { } // Table -> Static
}