Static Dispatch vs Dynamic Dispatch

Damla Çim
4 min readOct 7, 2024

--

Hey folks! Today we’re going to talk about the two sides of performance and polymorphism.

Swift, like all modern languages, strongly supports the object-oriented programming approach. However, managing polymorphism and method calls — essential features of object-oriented programming — can directly impact a program’s performance. This is where Swift introduces two key mechanisms: static dispatch and dynamic dispatch (V-Table).
In the simplest terms, these mechanisms determine whether a method will be called at runtime or compile time.

Static Dispatch

Static dispatch decides which version of a method will be called at compile time. Since the compiler can determine the method in advance (the addresses are known), there is no extra load at runtime. Therefore, it is the fastest type of method call. It is supported by both value types (structs, enums) and reference types (final classes). Static dispatch and inheritance are generally not used together because static dispatch does not support features like polymorphism or overridden methods.

Dynamic Dispatch

In dynamic dispatch, method calls are resolved at runtime. This mechanism uses a virtual table (V-Table) for calling methods.

What is V-Table?

A V-Table is especially useful for managing polymorphism and overridden methods. Each class has a table that keeps track of its virtual methods. This table dynamically manages method calls depending on the class the object belongs to.

Let’s examine it in more detail with an example.

We can use static dispatch in final classes or for final methods because they cannot be overridden. What makes final methods utilize static dispatch is that these methods cannot be changed by subclasses. Therefore, static dispatch is used since there is no dynamic analysis required for overrides.

What would dynamic dispatch look like in this example?

With the statement let account = PersonalAccount(), the variable account becomes an instance of the PersonalAccount class. When the statement account.accountType() is called, Swift checks the type of the account object (i.e., PersonalAccount) at runtime and calls the overridden version. In this case, the accountType() method in PersonalAccount is called instead of the one in the BankAccount class, and "personal account" is returned.

If dynamic dispatch were not used and static dispatch was employed, account.accountType() would always call the method of the class determined at compile time (in this case, BankAccount). That means it would return the account type from BankAccount, ignoring the overridden method in PersonalAccount.

There is also something important I want to mention.

We have a test class. This class is not inherited by another class, and the printA function is not overridden. Since I wrote the code, I know that this class and function are not used anywhere. However, Swift does not know this. If I had defined the class with the final keyword, I would have indicated: "I will not use this anywhere else, and there is no chance of it changing at runtime, so use static dispatch." The compiler reads the code and reaches the TestA class. After this point, it won’t know whether the class will be inherited, so it defaults to dynamic dispatch.

We encountered this case while discussing the article with a former colleague, and we thought it might be useful to mention.

How to Optimize Dispatch?

Optimizing dispatch in Swift is about balancing the trade-offs between dynamic dispatch (which provides flexibility at the cost of performance) and static dispatch (which is more performant but less flexible). There are several ways to optimize dispatch:

1. Use the final Keyword

As mentioned earlier, declaring a class or method as final prevents it from being subclassed or overridden. This allows the compiler to use static dispatch instead of dynamic dispatch.

2. Use struct and enum Instead of class

struct and enum in Swift always use static dispatch because they do not support inheritance. Since there’s no need for dynamic dispatch, using value types like struct and enum can provide a performance boost as methods are resolved at compile time.

3. Avoid Overriding Methods if Not Necessary

Avoiding method overrides reduces the need for dynamic dispatch. If you know a method won’t be overridden, you can mark it as final. Overriding methods forces Swift to use dynamic dispatch, as it cannot determine at compile time which method will be called at runtime.

4. Use the @inlineable Attribute

The @inlineable attribute allows a method to be inlined by the compiler, meaning that the method call is inserted directly into the place where it is called. Normally, when a function is called, program control goes to where the function is defined, performs the operation, and then returns. This process can incur a performance penalty, especially for small functions that are called frequently. Inlining helps eliminate this cost, improving performance.

We compared static dispatch and dynamic dispatch and discussed various ways to optimize dispatch for performance.

Thanks for reading! 🤠

--

--

Damla Çim

iOS developer at Garanti BBVA Technology | computer engineer