“Final” keyword — Swift 5 (iOS)
Understanding the Final keyword and decoding the mystery.
Okay! Let’s get right at it! For all of us, iOS developers, there comes a time when we encounter Final in the code. At that time we ask ourselves what does it do? Where to use it? How to use it? When not to use it? And many other questions start popping up in our heads.
Here, I will try to answer these questions, and hopefully, decode the mystery of final
by the end.
Implications of using final
- It prevents overriding and inheritance.
- It increases the runtime performance of the code.
But what does this mean??? Let’s see!
Prevent Inheritance and Overriding
Swift like most other object-oriented languages allows a class to inherit from another class and override methods and properties declared in its superclass.
However, this is not always desirable and ideal. There are many cases where we do not want anyone or any other part of the code to change the implementation of the class — such a class is to be used the way it is and any change is undesirable.
In such cases, declaring the class as final
will prevent it from being subclassed and thus, will prevent member overriding.
Similarly, there are cases when we do not want to change the implementations of a few members of the class. In that case, we do not want to override these few members. The way to do so is to declare themfinal.
Note — Please ignore edge case handling in User class member functions.
Enhance Runtime Performance
Saving time and avoiding any compiler computation at runtime makes one’s program efficient and fast. This is what makes final
more interesting and useful for any programmer like me.
Swift being an OOP language allows defining class and inheritance. This means there can be classes and subclasses and subclasses may/may not override properties and methods of the base class.
However, when we look at this from the system’s point of view, every time a class member is referred (no matter from the subclass or the base class), the system must perform the following computations at runtime —
- Check if the class is inherited anywhere in the code or is itself a subclass.
- Check if any of the class members are overridden and the number of times. And what possible implementations are available for the referenced member.
- Get the correct implementation out of the multiple overrides.
- Indirectly call the correct implementation from a pool of implementations.
These computations increase the expressivity of the language but add to the runtime overhead as they require more time and power per call. This decreases the overall efficiency of the code.
This entire chain of events of indirectly accessing the referenced member is termed as Dynamic Dispatch by Apple. But is it necessary to go through Dynamic Dispatch all the time — NO! The question that naturally arises then is how can one get rid of dynamic dispatch? How to write efficient code?
The answer is final
. When we declare a member final
, it not only specifies that it cannot be inherited/overridden but it also notifies the system that — Hey! No other similar entity exists. This ensures that the system need not worry and directly implement the one defined — and this happens at Compile time. Thus, removing the runtime overhead. This is called direct access or Static Dispatch — which happens at compile time.
This means by merely adding the word final
, we can shift the entire processing from runtime to compile time. Hence, increasing the runtime efficiency.
In the above snippet, when User
is instantiated, Dynamic dispatched calls are sent —
- to variables —
firstName,
secondName.
- to methods —
init(:),
getChars(:),
generateKey(:).
Also, in the above snippet, whenever user
is accessed, it is done via Dynamic dispatched calls.
Here, even though a single User
class exists, still Dynamic Dispatch is compulsorily used. This is because the system has no knowledge if a subclass of User
exists, or if any member is overridden anywhere. The system can gather this information either by looking up for them (which is dynamic dispatch) or if we explicitly notify it of their non-existence (which is handled by final
).
So to sum it up — The above snippet shows a general use-case.
- Here the members are declared
final
to prevent overriding them. - Declaring them
final
eliminates runtime overheads and implements a Static Dispatch.
This is all for now! Thank you for reading. Hope you found it informational.
Happy Coding 💻🖥
Here are some references to let you explore more about the topic —
- Apple blog — https://developer.apple.com/swift/blog/?id=27
- Different dispatches in Swift — https://heartbeat.fritz.ai/understanding-method-dispatch-in-swift-684801e718bc
- Swift documentation on
final
— https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html #Preventing Overrides.