A few years ago, the Apple team published an interesting article
Improving Productivity by Reducing Dynamic Dispatch . This article is quite interesting to read, which highlights the subtle aspects of the Swift language and its compiler.
In today's article, I want to talk about performance in Swift and how it is affected by access control. Access control is a mechanism that novice developers sometimes overlook. The purpose of this article is to show you how important it is to think about the code you are writing, and how each line of code will fit into the big picture.
A bit about access control
Access control in the Swift language is easy to learn. The access levels and their definitions have changed a bit in recent years, and now, I think, there is a reliable solution for access control in Swift.
')
If you use
Objective-C , it may take some time before you understand the benefits of access control. Effective use of access levels is one of the concepts that separates a novice developer from a more experienced one. Let me show you why this is so.
More than defining access levels
Access control may not seem very useful if you are working on your own or in a small team. It is true that access control is really useful if you are developing a framework, library or SDK integrated into other software projects. However, if you think that access control is useful or necessary only if you are working on a source code base for distribution and use by third parties, then you are mistaken.
Access control has many advantages, some of which are easy to overlook. An obvious advantage with proper access control is communication. By attaching the
private keyword to a class instance method, you implicitly state that the instance method should not be overridden by subclasses. With one carefully chosen keyword, your code speaks for itself. Every developer understands why an instance method cannot be overridden if it is declared private.
Swift performance improvements
However, access control has side effects that many beginning Swift developers are not aware of. Did you know that the compiler checks the access levels that you used to optimize the performance of your code? That's what I want to talk about today.
To understand how access control can lead to more efficient software, we need to deviate from the topic and talk about the Send Method to Swift. Do not worry. I will consider only the basics. This is just a technical deviation from the route, but I promise you that it will be interesting.
What actually is the Method Dispatch.
When you call an object method or access one of its properties, a message is sent to that object. The execution time should determine which method matches the message. Take a look at this example.
window.makeKeyAndVisible()
We call the
makeKeyAndVisible () method for the window object, an instance of
UIWindow . At run time, the message is sent to the window object. Although it may seem obvious to you which method you need to call for the message, this is not always the case.
What happens if we are dealing with a subclass of
UIWindow that replaces the
makeKeyAndVisible () method? The runtime must determine if you need to call the
makeKeyAndVisible () method for a subclass or
superclass .
A method dispatch is a set of rules that the runtime uses to determine the method that should be invoked for a given message. Swift relies on three types: direct dispatch, table dispatch, and message dispatch. Direct shipping is also called static shipping. Sending messages and tables are types of dynamic sending.
Dynamic Dispatch / Dynamic Dispatch
Messaging is the authority of Objective-C and the Objective-C runtime. Each sent message is sent dynamically. What does it mean? The Objective-C runtime environment shows which method to call for a message at runtime by checking the class hierarchy. That is why Objective-C is such a dynamic language. Objective-C agility is also an ability of several
Cocoa functions, including
Key-Value Observing , and a behavior model.
For dynamic submission there is one important drawback. Since the runtime must determine which method to call for the message, dynamic sending is in itself slow compared to direct sending. In other words, a dynamic dispatch comes with little overhead.
Static Dispatch / Static Dispatch
Static sending, also known as direct sending, but it has its own differences. The compiler can determine at compile time which method should be called for the message. As the name suggests, this is not a dynamic dispatch. What is lost in flexibility and dynamism is achieved in performance.
The executable environment does not need to determine which method to call at run time. The small performance associated with dynamic sending is simply missing when using direct sending.
Performance optimization
Although I will not dig deeper into the study of sending methods, you need to remember one thing - static sending is more effective than dynamic sending. To improve performance, the task of the compiler is to push as far as possible the method calls from dynamic to static dispatch.
Optimization through access control
While Objective-C relies solely on sending messages, Swift uses a combination of direct, tabular sending and sending messages. Swift prefers static sending. To focus on the discussion, I look at static and dynamic posting for the remainder of this article.
Inheritance is a powerful paradigm, but at the same time it becomes more difficult for the compiler to determine exactly how to call the method. Take a look at this example.
import UIKit class ViewController: UIViewController { // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad()
The
ViewController class defines the
fetchNotes () method. You probably know that methods and properties are declared internal by default, which means that a method or property is available to other objects that are defined in the same module. Is it enough to declare
fetchNotes () as
internal ? This has a certain dependency.
Since we have
linked the internal keyword to the
fetchNotes () method, a subclass of
ViewController can override the
fetchNotes () method. As a result, the compiler cannot determine which implementation to execute when calling the
fetchNotes () method. The runtime requires dynamically send calls to the
fetchNotes () method
Analysis of the code that you write
When more experienced developers look at the code they write, they analyze how it fits into the project they are working on. The implementation of the method is only part of the solution. Is it necessary for subclasses of
ViewController to override the
fetchNotes () method? If the answer is no, then you must attach the keyword private or fileprivate. This not only makes sense in the context of access control, but also improves performance. Why is this so?
When the compiler checks the
fetchNotes () method, it understands that it is declared private, meaning that this method cannot be overridden by a subclass. The compiler selects this hint and safely displays the final on the method declaration. Whenever the
final keyword is attached to a method declaration, calls to this method can be sent statically instead of a dynamic method, which results in a slight increase in performance.
Full module optimization
This article will not be complete without mentioning the optimization of the entire module. The Swift compiler is an amazing piece of software engineering, and it has many fantastic features that we don’t know about. One of these great features is the optimization of the entire module.
Module optimization is disabled by default for debug builds. This results in a shorter compile time, but you pay a price for the save time. Without optimizing the entire module, each file in your project is compiled separately, without taking into account the rest of the source code base. This is good during development.
However, when you create your project for further distribution, optimizing the entire module allows you to optimize the performance of your application. The compiler no longer processes each file separately. He creates a puzzle that is your project. What does this mean and why does it matter?
Take another look at the code snippet I showed you before. Remember that the call to
fetchNotes () is dynamically sent at runtime. If optimization of the whole module is enabled, then this is not the case. When the compiler checks the entire module, your project and determines how each file fits into the big picture, it detects the absence of ViewController subclasses that override the
fetchNotes () method. This means that the compiler can conclude final on the declaration of the
fetchNotes () method.
The
final keyword means that a method or property cannot be redefined in subclasses. The result we saw earlier is that calls to
fetchNotes () can be sent in a static way, even if
fetchNotes () is not declared private. Smart compiler. Is not it?
We continue training.
I often write about developer development, emphasizing how important it is to invest in education. Studying the more subtle details of Swift has changed me as a developer. The code I write today is different from the code I wrote a year ago.
Although sending a method may seem like a cutting-edge topic, I believe that this is just as important as the study of automatic reference counting or protocol oriented programming. Swift is easy to grasp, and that's good. If you are serious about becoming an excellent developer, it is important to continue learning and expand horizons.