Method dispatching mines in Swift
First of all, let’s do a quick test. What do you think will be the output of this program?
class A {
func execute(ind: Int = 0) {
print("A: \(ind)")
}
}
class B: A {
override func execute(ind: Int = 1) {
print("B: \(ind)")
}
}
let instance: A = B()
instance.execute()
The output is “B: 0”
Please continue reading (I promise to keep it short 😊) to understand how did this happen.
Method Dispatch
Method dispatching is when a program is executing and faces a method call. The program needs to dispatch to the address of this method’s implementation. Sometimes we expect that the implementation for a method is determined only at runtime.
Swift provides multiple dispatching mechanisms sorted by speed:
- Static Dispatch
- V-Table Dispatch
- Message Dispatch
In this article, we will be referring to the V-Table Dispatch and Message Dispatch as “Dynamic Dispatch”. They both have the same behavior of identifying which implementation to be used at runtime but with different performances.
We will be more focusing on the differences between Static and Dynamic dispatch.
Dynamic Dispatch
This is a mechanism of choosing the method implementation to be used at runtime. So, when the program hits a function call, it starts searching for the correct implementation that should be executed and jumps to it. This searching step makes it an overhead which makes it slower.
Why is it being used? Because it is flexible. Thanks to dynamic dispatch, developers can now define the method only one time and provide multiple implementations for it. The compiler with the help of the dynamic dispatch can now choose the correct implementation for this method.
Most of us are using the power of dynamic dispatch in our daily development without noticing it; It allows the existence of Polymorphism and the Protocol pattern. As mentioned, in these cases you only need to define the method in 1 place and implement it in multiple classes and the dynamic dispatch will take care of you.
Static Dispatch
Sometimes referred to as “Direct dispatch”. At compile time the compiler already knows which implementation will be used if the method is called. So, when the function is called the program jumps directly to the address that was generated while compiling.
Deciding the dispatching mechanism
Swift always tries to prioritize static dispatching for better performance.
Examples
Value Types
Since structs
and enums
are value types, which don’t support inheritance, all method calls will use static dispatch. The compiler knows there will be only 1 implementation for each method at runtime.
Protocol
All methods defined in the protocol
itself will be dispatched dynamically but any method defined inside a protocol
's extension will be dispatched statically.
protocol Shape {
func draw() // Dynamic
}
extension Shape {
func area() {
print("area") // Static
}
}
What do you think about this? pretty straightforward and easy to understand, correct? Unfortunately, it is not. Here comes our first mine
Mine #1
protocol Shape {
func draw()
}
extension Shape {
func draw() {
print("Shape")
}
func area() {
print("Shape")
}
}
class Circle: Shape {
func draw() {
print("Circle")
}
func area() {
print("Circle")
}
}
let circle: Shape = Circle()
circle.draw() // "Circle", Dynamically dispatched
circle.area() // "Shape", Statically dispatched
Here the area
method is statically dispatched but we also implemented the same method in the Circle
class which will take no effect as the compiler will select Shape
's default implementation to be executed.
This result is too weird and may take hours to debug if you’re not aware of the static/dynamic dispatching
Class
As a class can be inherited, the default dispatching mechanism is Dynamic dispatch.
It’s the developer’s responsibility to help the compiler optimize its performance. How?
The compiler will dispatch the method statically in any of these cases:
- The
class
is markedfinal
- The method is marked
private
orfinal
. As aprivate
method can’t be overridden by subclasses - The method is defined in an extension
Note that when annotating a method with
@objc
ordynamic
will override the dispatching mechanism to be dynamic dispatch
class A {
func foo() { } // Dynamic
private func bar() { } // Static
final func bas() { } // Static
}
extension A {
func doWork() { } // Static
}
final class B {
func doWork() { } // static
}
So a nice to do would be to mark any new class with final
upon creation and remove it when inheritance is needed. Also, mark all methods private
and remove them when needed.
Mine #2
Back to the snippet from the top
class A {
func execute(ind: Int = 0) {
print("A: \(ind)")
}
}
class B: A {
override func execute(ind: Int = 1) {
print("B: \(ind)")
}
}
let instance: A = B()
instance.execute()
How did this happen? Here we are calling A
's method which will use its default value (0) but will dynamically dispatch the implementation only. You will end up with B
's implementation and A
's method definition.
Where to go
If you want to deep dive you can read this article which I have used for inspiration.
You can connect with me on Linkedin.