A few years ago, Apple's Swift team posted an interesting article titled Increasing Performance by Reducing Dynamic Dispatch. It is a very interesting read, highlighting some of the more subtle aspects of the Swift language and the compiler.

In today's post, I would like to zoom in on Swift performance and how it is affected by access control. Access control is a feature that is sometimes overlooked by developers new to the Swift programming language. The goal of this post is to show you how important it is to think about the code you write and how every line fits into the bigger picture.

A Word About Access Control

Access control in Swift isn't difficult. Access levels and their definitions have evolved a little over the years and I feel we now have a solid solution for implementing access control in Swift.

If you are coming from Objective-C, then it may take a while before you understand the benefits of access control. Effectively using access levels is one of the things that sets a junior developer apart from a more experienced developer. Let me show you why that is.

More Than Defining Access Levels

Access control may not seem that useful if you work alone or as part of a small team. It is true that access control really shines if you are developing a framework, library, or SDK that is integrated in other software projects. However, thinking that access control is only useful or necessary if you are working on a codebase that is distributed and used by third parties is a misconception.

Access control has many benefits, some of which are subtle and easily overlooked. An obvious benefit of properly applying access control is communication. Even if you are working in a team of one, it pays to learn and correctly apply access control in your projects. By prefixing an instance method of a class with the private keyword, you implicitly communicate that the instance method should not be overridden by subclasses. With one carefully chosen keyword, you have documented your code. Every Swift developer understands why the instance method cannot be overridden if it is declared as private.

Improving Swift Performance

There is much more, though. Access control has side effects that many aspiring Swift developers are unaware of. Did you know that the compiler inspects the access levels you apply to optimize the performance of your code? That is what I want to talk about in this post.

To understand how access control can result in more performant software, we need to take a detour and talk about method dispatch in Swift. Don't worry, though. I only touch on the basics. It is a technical detour, but I promise you that it is an interesting one.

What Is Method Dispatch?

When a method is invoked on an object or one of its properties is accessed, that object is sent a message. The runtime needs to figure out which method corresponds with the message. Take a look at this example.

window.makeKeyAndVisible()

We invoke the makeKeyAndVisible() method on the window object, a UIWindow instance. At runtime, a message is sent to the window object. While it may seem obvious to you which method needs to be invoked for the message, that isn't always the case.

What happens if we are dealing with a UIWindow subclass that overrides the makeKeyAndVisible() method? The runtime needs to figure out if it needs to invoke the makeKeyAndVisible() method of the subclass or the superclass.

Method dispatch is the set of rules the runtime utilizes to infer which method to invoke for a particular message. Swift relies on three types of method dispatch, direct dispatch, table dispatch, and message dispatch. Direct dispatch is also referred to as static dispatch. Table and message dispatch are types of dynamic dispatch.

Dynamic Dispatch

Message dispatch is what powers Objective-C and the Objective-C runtime. Every message that is sent is dispatched dynamically. What does that mean? The Objective-C runtime figures out which method to invoke for a message at runtime by inspecting the class hierarchy. That is why Objective-C is such a dynamic language. Objective-C's dynamism is also what powers several Cocoa features, including Key-Value Observing and the target-action pattern.

There is one important downside to dynamic dispatch. Because the runtime needs to figure out which method to invoke for a message, dynamic dispatch is slow compared to direct dispatch. In other words, dynamic dispatch comes with a bit of overhead.

Static Dispatch

Static dispatch, also known as direct dispatch, is different. The compiler can infer at compile time which method to invoke for a message. As the name implies, this isn't dynamic. What is lost in flexibility and dynamism is gained in performance.

The runtime doesn't need to figure out which method to invoke at runtime. The small performance hit dynamic dispatch suffers from is absent if static or direct dispatch is used.

Optimizing for Performance

While I won't dig deeper into method dispatch in this post, I want you to remember that static dispatch is more performant than dynamic dispatch. To improve performance, the task of the compiler is to promote method invocations from dynamic to static dispatch as much as possible.

Optimization Through Access Control

While Objective-C relies exclusively on message dispatch, Swift uses a combination of direct, table, and message dispatch. It favors static dispatch over dynamic dispatch. To keep the discussion focused, I only consider static and dynamic dispatch in the remainder of this post.

Inheritance is a powerful paradigm, but it makes it more difficult for the compiler to figure out which method to invoke for a message. Take a look at this example.

import UIKit

class ViewController: UIViewController {

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // Fetch Notes
        fetchNotes()
    }

    // MARK: - Helper Methods

    func fetchNotes() {
        ...
    }

}

The ViewController class defines the fetchNotes() method. You probably know that methods and properties are declared as internal by default, which means that the method or property is accessible by other entities defined in the same module. Is it sufficient to declare fetchNotes() as internal? It depends.

Because we attached the internal keyword to the fetchNotes() method, it is possible for a ViewController subclass to override the fetchNotes() method. The result is that the compiler is unable to figure out at compile time which implementation to execute when the fetchNotes() method is invoked. The runtime needs to dynamically dispatch invocations of the fetchNotes() method.

Analyze the Code You Write

When more experienced developers look at the code they write, they consider how it fits into the project they are working on. The implementation of a method is only part of the solution. Should it be possible for ViewController subclasses to override the fetchNotes() method? If the answer is no, then you should attach the private or fileprivate keyword. This not only makes sense in the context of access control, it also improves performance. Why is that?

When the compiler inspects the fetchNotes() method, it realizes that it is declared as private, implying that the method cannot be overridden by subclasses. The compiler picks up this clue and safely infers final on the method declaration. Whenever the final keyword is attached to a method declaration, calls to that method can be dispatched statically instead of dynamically, resulting in a tiny performance gain.

Whole Module Optimization

This post wouldn't be complete without a mention of Whole Module Optimization. The Swift compiler is an amazing piece of engineering and it sports a slew of fantastic features we aren't aware of. One of these nifty features is Whole Module Optimization.

Whole Module Optimization is disabled by default for debug builds. This results in shorter compile times, but you pay a price for the time you save. Without Whole Module Optimization enabled each file in your project is compiled separately, not taking the rest of the codebase into account. That is fine during development.

When you build your project for distribution, however, Whole Module Optimization kicks in to optimize the performance of your application. The compiler no longer treats each file separately. It builds the puzzle that is your project. What does that mean and why is that important?

Take another look at the code snippet I showed you earlier. Remember that a call to fetchNotes() is dynamically dispatched at runtime. That isn't true if Whole Module Optimization is enabled. When the compiler inspects the entire module, your project, and figures out how each file fits into the bigger picture, it discovers that there are no ViewController subclasses overriding the fetchNotes() method. That means the compiler can infer final on the fetchNotes() method declaration.

The final keyword means that a method or property cannot be overridden in subclasses. The result, as we saw earlier, is that calls to fetchNotes() can be statically dispatched, even if fetchNotes() isn't declared private. That is one smart compiler. Isn't it?

Keep Learning

I often write about growing as a developer and I emphasize how important it is to invest in your education. Learning about the finer details of the Swift language has changed me as a developer. The code I write today is different from the code I wrote a year ago.

While method dispatch may seem like an advanced topic, I feel it is as important as learning about Automatic Reference Counting or protocol-oriented programming. The Swift language is easy to pick up and that is a good thing. If you are serious about becoming a great developer, though, it is important to continue learning and to push the envelope.