Swift's final keyword is often overlooked or dismissed as not necessary. Applying the final keyword is a subtle technique to optimize your code in a powerful way. Let me explain what I mean by that.

Inheritance

If we discuss the final keyword, then we also need to talk about inheritance. It makes no sense to prefix a type definition of a value type with the final keyword. In fact, the compiler will throw a compile time error if you do.

Swift's final keyword cannot be applied to value types.

Why is that? If you prefix a class definition with final, you prevent anyone from subclassing that class. Value types don't support inheritance so it doesn't make sense to mark a struct or enum as final.

The final keyword communicates to the compiler that the class cannot and should not be subclassed. Applying the final modifier to a class definition is a clear signal.

final class Person {
    
}

Applying final to a class isn't the only option you have. It is possible to apply final in a more subtle form. Any method, property, or subscript that is prefixed with the final modifier cannot be overridden. This is useful if a property, method, or subscript cannot be declared as private or fileprivate, but you don't want subclasses to override it.

class Car {
    
    // MARK: - Properties
    
    var name: String
    
    // MARK: -
    
    final var canDrive: Bool
	
    ...    
    
}

Whole Module Optimization

The compiler marks entities as final if it knows it can safely do so. What do I mean by that? The compiler uses Whole Module Optimization to optimize the code you write. As the name suggests, it inspects and evaluates the code at the level of the module.

When Whole Module Optimization is enabled, the compiler evaluates each file in the context of the module. It puts the pieces of your project together to better understand how it is architected. Take a look at this example.


We define a class with name Car. Car defines a property, name, of type String and a method with name drive().

class Car {
    
    // MARK: - Properties
    
    var name: String {
        "Car"
    }
    
    // MARK: - Methods
    
    func drive() {
        // ...
    }

}

Car has a subclass, FastCar. FastCar overrides the name property, but it doesn't override the drive() method.

class FastCar: Car {
    
    // MARK: - Properties
    
    override var name: String {
        "Fast Car"
    }
    
}

When Whole Module Optimization is enabled, the compiler inspects the implementations of Car and FastCar and looks for ways to optimize. It notices that FastCar subclasses Car and that it overrides the name property. Because FastCar doesn't override the drive() method, the compiler can infer final on the drive() method.

class Car {
    
    // MARK: - Properties
    
    var name: String {
        "Car"
    }
    
    // MARK: - Methods
    
    final func drive() {
        // ...
    }

}

Even though we forgot to apply the final keyword to the drive() method, the compiler didn't. This is one example. The compiler performs many more optimizations to create a build that is fast and performant.

Does this mean that you don't need to apply the final keyword? Absolutely not. Don't forget what I wrote earlier. The final keyword has several applications and optimizing performance is only one aspect.

Whole Module Optimization is disabled by default for debug builds. This means that debug builds take less time to compile, but it implies that debug builds aren't as optimized as distribution builds.

Need Help?

I use SwiftLint in most of the projects I work on because it is easy to forget best practices. It happens—more than I care to admit—that I forget to mark a class that isn't subclassed as final. SwiftLint kindly reminds me with a warning.