Static vs Dynamic Dispatch in Swift: A decisive choice

Shubham Bakshi
7 min readJun 24, 2019

--

Discover the enhanced version of the article, complete with refined syntax highlighting and all the latest updates, now available on my personal blog! Your feedback and engagement are greatly appreciated and help fuel future content:

Well if you’re an OOP fan, these method dispatch techniques(especially dynamic dispatch) might not be new to you.

Method Dispatch is the mechanism that helps to decide which operation should be executed, or more specifically, which method implementation should be used.

Basics

To start things off, Static Dispatch is supported by both value types and reference types.

However, Dynamic Dispatch is supported only by reference types(i.e. Class). The reason for this is that, for dynamism, or dynamic dispatch, in short, we need inheritance and our value types do not support inheritance.

Keeping that thing in mind, let’s move forward!

To get the big picture, there are not 2 (Static and Dynamic) but rather 4 types of dispatch techniques —

  1. Inline (Fastest)
  2. Static Dispatch
  3. Virtual Dispatch
  4. Dynamic Dispatch (Slowest)

It is up to the compiler to decide which dispatch technique should be used, giving preference to Inline and then going down as and when required.

Static vs Dynamic or Swift vs Objective-C

By default, Objective-C supports Dynamic dispatch. This dispatch technique provides flexibility to the developer in the form of Polymorphism! Subclassing and overriding existing methods and stuff, which is great. But, it comes at a cost.

Dynamic dispatch increases language expressivity at the cost of a constant amount of runtime overhead. What that means is that for every call to a method, in case of dynamic dispatch, our compiler will have to look inside what we call a witness table (virtual table or dispatch table in other languages) for checking the implementation of that particular method. The compiler needs to determine whether you referring to the superclasses’ implementation or are you referring to the implementation of the subclass. And since memory to all the objects is allocated at runtime, the compiler can only perform that check at runtime.

Static dispatch, however, does not have this problem. With this dispatch technique, the compiler knows, at compile-time, which method implementation is to be called for a method. Thus the compiler can perform certain optimizations and can even convert the code to Inline, if possible, thus making the overall execution speed insanely fast!

Now how do we achieve both of them in Swift ?

  • To achieve Dynamic Dispatch, we use inheritance, subclass a base class and then override an existing method of the base class. Also, we can make use of dynamic keyword and we need to prefix it with @objc keyword so as to expose our method to Objective-C runtime
  • To achieve Static Dispatch, we need to make use of final and static as both of them ensures that the class and method cannot be overridden

Let’s dive deep

Static Dispatch (or Direct Dispatch)

As explained above, they are pretty fast when compared to Dynamic dispatch as the compiler is able to locate where the instructions are, at compile time. So, when the function is called, the compiler jumps directly to the memory address of the function to perform the operation. This result is huge performance gains and certain compiler optimizations as well such as inlining.

Dynamic Dispatch

As explained earlier, in this type of dispatch, the implementation is chosen at runtime instead of compile-time, which adds some overhead.

Now, you might be wondering, if it’s so expensive, why do we even use it .

Well, because of its flexibility. In fact, most of the OOP languages support Dynamic Dispatch because it allows polymorphism to exist.

Now there are two types of dynamic dispatch —

  1. Table Dispatch

This dispatch technique makes use of a table, which, is an array of function pointers, called as witness table (or virtual table) to look up for the implementation of a particular method.

Now, how does this witness table works ?

  • Every subclass has its own copy of the table
  • This table has different function pointers for every method that this class has overridden
  • As the subclass add new methods, those method pointers are appended to the end of this array
  • Finally, the compiler, at runtime, makes use of this table to see which implementation is to be called for a method

Since the compiler has to read the memory address for the implementation from the table and then jump to that address, it required two additional instructions, so it is slower than static dispatch but it is still faster than message dispatch.

NOTE : I am not so sure but this particular dispatch technique can be Virtual Dispatch as it makes use of virtual table but i was not able to find a concrete reference

2. Message Dispatch

This dynamic dispatch technique is the most dynamic (pun intended ) out there. It is, in fact, so good (leaving out the optimization part), that Cocoa frameworks use it inside a lot of its big players like KVO, Core Data, and other things.

Also, it enables method swizzling, which generally means that using this technique, we can change the functionality of a method at runtime.

Now, the Swift compiler does not provide this out-of-the-box. It rather makes use of Objective-C runtime to achieve this dispatch technique.

To explicitly make use of this dispatch, we need to make use of dynamic keyword. Prior to Swift 4.0, whenever we used dynamic, @objc was implicitly added but starting Swift 4.0, we need to explicitly mark it with @objc to make our method to be exposed to Objective-C runtime and thus message dispatch.

Since we are using Objective-C runtime for this, when a message is dispatched, the runtime will crawl the class hierarchy to determine which method to invoke. This is really slow. So to make up for its performance, it provides us with a cache which makes somewhat of a difference

The compiler always try to upgrade the dispatch technique to static dispatch unless we have explicitly marked it with dynamic keyword

Examples

Value Types

struct Person {
func isIrritating() -> Bool { } // Static
}

extension Person {
func canBeEasilyPissedOff() -> Bool { } // Static
}

Since struct and enum are value types and does not support inheritance, the compiler puts it under Static dispatch as it is aware of the fact that it can never be subclassed.

Protocol

protocol Animal {
func isCute() -> Bool { } // Table
}

extension Animal {
func canGetAngry() -> Bool { } // Static
}

The key point to note here is that any method defined inside extension uses Static Dispatch

Class

class Dog: Animal {
func isCute() -> Bool { } // Table
@objc dynamic func hoursSleep() -> Int { } // Message
}

extension Dog {
func canBite() -> Bool { } // Static
@objc func goWild() { } // Message
}

final class Employee {
func canCode() -> Bool { } // Static
}
  • Normal method declarations follow the same principles as protocol
  • Whenever we expose a method to Objective-C runtime using @objc, the method uses Message Dispatch
  • However, if we mark a class as final, that class cannot be subclassed and thus its methods use Static Dispatch.
To sum it all up !

Okay, now that’s just me telling and you believing everything I say, Right?

Now how to prove that these methods are actually using the dispatch techniques that i explained above?

For that, we have to take a look inside Swift Intermediate Language . From what I could research online, i found that there is a way —

  1. If a function uses Table dispatch, it appears in the vtable (or witness_table )
sil_vtable Animal {#Animal.isCute!1: (Animal) -> () -> () : main.Animal.isCute() -> () // Animal.isCute()......}

2. If a function uses Message Dispatch, the keyword volatile should be. present in the invocation. Also, you will find the two marker foreign and objc_method, indicating that the function is invoked using Objective-C runtime.

%14 = class_method [volatile] %13 : $Dog, #Dog.goWild!1.foreign : (Dog) -> () -> (), $@convention(objc_method) (Dog) -> () 

3. If there is no evidence of the two cases above, the answer is Static dispatch.

Well, that’s it from my side! I planned this to be a two-article series and the upcoming article (now available here) will be on performance comparison between Static and Dynamic dispatch through Test Cases.

Content Inspiration : Method dispatch in Swift — Thuyen’s corner

You can connect with me on LinkedIn 👱🏻 or can get in touch with me via other channels 📬

--

--