Performance Improvement Through Access Control
- Transfer
A few years ago, the Apple team published an interesting article Improving Productivity by Reducing Dynamic Sending . This article is quite interesting to read, which highlights the subtle aspects of Swift and its compiler.
In today's article, I want to talk about performance in Swift and how access control affects it. Access control is a mechanism that is sometimes overlooked by novice developers. The purpose of this article is to show you how important it is to ponder the code you write and how each line of code will fit into the big picture.
Access control in Swift is easy to learn. Access levels and their definitions have changed a bit in recent years, and now, I think, there is a reliable access control solution 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.
Access control may not seem very useful if you work 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. The obvious advantage with proper access control is communication. By attaching the private keywordto the class instance method, you implicitly report that the instance method should not be overridden by subclasses. With one carefully selected keyword, your code speaks for itself. Each developer understands why the instance method cannot be overridden if it is declared private.
However, access control has side effects that many new 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? This is what I want to talk about today.
To understand how access control can lead to more efficient software, we need to digress and talk about Submitting a 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.
When you call a method of an object or access one of its properties, a message is sent to this object. The runtime should determine which method matches the message. Take a look at this example.
We call the makeKeyAndVisible () method on 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 needs to be called 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 ? Runtime should determine whether to call the makeKeyAndVisible () method on a subclass or super class.
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 sending is also called static sending. Sending messages and tables are types of dynamic sending.
Message distribution 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 shows which method to invoke for a message at runtime, checking the class hierarchy. That's why Objective-C is such a dynamic language. The dynamism of Objective-C is also the ability of several Cocoa features , including Key-Value Observing , as well as a behavior model.
There is one important drawback to dynamic dispatch. Because the runtime needs to determine which method to invoke for the message, dynamic dispatch itself is slow compared to direct dispatch. In other words, dynamic dispatch comes with little overhead.
Static dispatch, also known as direct dispatch, but has its own differences. The compiler can determine at compile time which method to call for the message. As the name implies, this is not a dynamic submission. What is lost in flexibility and dynamism is achieved in productivity.
The runtime does not need to determine which method to call at runtime. The small performance associated with dynamic sending is simply not available when using direct sending.
Although I will not dig deeper into the study of method dispatch, you need to remember one thing - static dispatch is more efficient than dynamic dispatch. To improve performance, the task of the compiler is to promote method calls from dynamic to static dispatches as much as possible.
While Objective-C relies solely on sending messages, Swift uses a combination of direct, tabular, and message sending. Swift prefers static sending. To focus on the discussion, I look at static and dynamic dispatch in 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 the method is called. Take a look at this example.
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.
Because we bound 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 the fetchNotes () method is called . The runtime requires dynamically sending method callsfetchNotes ()
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. Do subclasses of ViewController need 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 productivity. Why is this so?
When the compiler checks the fetchNotes () method , it realizes that it is declared private, implying that this method cannot be overridden by a subclass. The compiler picks up this hint and safely outputs final to the method declaration. Whenever a keywordfinal is attached to the declaration of the method, calls to this method can be sent statically instead of the dynamic method, which leads to a small increase in performance.
This article would not be complete without mentioning the optimization of the entire module. The Swift compiler is an amazing creation 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 leads to a reduction in compilation time, but you pay the price for the save time. Without optimizing the whole module, each file in your project is compiled separately, without taking into account the rest of the source code database. This is good during development.
However, when you create your project for further distribution, optimization of 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 it mean and why is it important?
Let's look again at the code snippet that I showed you earlier. Remember that a call to fetchNotes () is dynamically dispatched at runtime. If the optimization of the entire module is enabled, then this is not so. When the compiler checks the entire module, your project and determines how each file fits into the large picture, it detects the absence of subclasses of ViewController that override the fetchNotes () method. This means that the compiler can infer final on the declaration of the fetchNotes () method .
The final keyword means that a method or property cannot be overridden 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?
I often write about developer development, emphasizing how important it is to invest in education. Learning 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.
Although submitting a method may seem like a cutting-edge topic, I find it as important as learning about automatic reference counting or a protocol-oriented programming. Swift is easy to grasp, which is good. If you are serious about becoming a great developer, it’s important to continue learning and expand your horizons.
In today's article, I want to talk about performance in Swift and how access control affects it. Access control is a mechanism that is sometimes overlooked by novice developers. The purpose of this article is to show you how important it is to ponder the code you write and how each line of code will fit into the big picture.
A bit about access control
Access control in Swift is easy to learn. Access levels and their definitions have changed a bit in recent years, and now, I think, there is a reliable access control solution 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 work 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. The obvious advantage with proper access control is communication. By attaching the private keywordto the class instance method, you implicitly report that the instance method should not be overridden by subclasses. With one carefully selected keyword, your code speaks for itself. Each developer understands why the instance method cannot be overridden if it is declared private.
Performance Improvement in Swift
However, access control has side effects that many new 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? This is what I want to talk about today.
To understand how access control can lead to more efficient software, we need to digress and talk about Submitting a 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 Method Dispatch really is.
When you call a method of an object or access one of its properties, a message is sent to this object. The runtime should determine which method matches the message. Take a look at this example.
window.makeKeyAndVisible()
We call the makeKeyAndVisible () method on 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 needs to be called 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 ? Runtime should determine whether to call the makeKeyAndVisible () method on a subclass or super class.
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 sending is also called static sending. Sending messages and tables are types of dynamic sending.
Dynamic Dispatch
Message distribution 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 shows which method to invoke for a message at runtime, checking the class hierarchy. That's why Objective-C is such a dynamic language. The dynamism of Objective-C is also the ability of several Cocoa features , including Key-Value Observing , as well as a behavior model.
There is one important drawback to dynamic dispatch. Because the runtime needs to determine which method to invoke for the message, dynamic dispatch itself is slow compared to direct dispatch. In other words, dynamic dispatch comes with little overhead.
Static Dispatch
Static dispatch, also known as direct dispatch, but has its own differences. The compiler can determine at compile time which method to call for the message. As the name implies, this is not a dynamic submission. What is lost in flexibility and dynamism is achieved in productivity.
The runtime does not need to determine which method to call at runtime. The small performance associated with dynamic sending is simply not available when using direct sending.
Performance optimization
Although I will not dig deeper into the study of method dispatch, you need to remember one thing - static dispatch is more efficient than dynamic dispatch. To improve performance, the task of the compiler is to promote method calls from dynamic to static dispatches as much as possible.
Optimization through access control
While Objective-C relies solely on sending messages, Swift uses a combination of direct, tabular, and message sending. Swift prefers static sending. To focus on the discussion, I look at static and dynamic dispatch in 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 the method is called. 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 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.
Because we bound 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 the fetchNotes () method is called . The runtime requires dynamically sending method callsfetchNotes ()
Analysis of the code 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. Do subclasses of ViewController need 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 productivity. Why is this so?
When the compiler checks the fetchNotes () method , it realizes that it is declared private, implying that this method cannot be overridden by a subclass. The compiler picks up this hint and safely outputs final to the method declaration. Whenever a keywordfinal is attached to the declaration of the method, calls to this method can be sent statically instead of the dynamic method, which leads to a small increase in performance.
Full module optimization
This article would not be complete without mentioning the optimization of the entire module. The Swift compiler is an amazing creation 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 leads to a reduction in compilation time, but you pay the price for the save time. Without optimizing the whole module, each file in your project is compiled separately, without taking into account the rest of the source code database. This is good during development.
However, when you create your project for further distribution, optimization of 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 it mean and why is it important?
Let's look again at the code snippet that I showed you earlier. Remember that a call to fetchNotes () is dynamically dispatched at runtime. If the optimization of the entire module is enabled, then this is not so. When the compiler checks the entire module, your project and determines how each file fits into the large picture, it detects the absence of subclasses of ViewController that override the fetchNotes () method. This means that the compiler can infer final on the declaration of the fetchNotes () method .
The final keyword means that a method or property cannot be overridden 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 the training.
I often write about developer development, emphasizing how important it is to invest in education. Learning 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.
Although submitting a method may seem like a cutting-edge topic, I find it as important as learning about automatic reference counting or a protocol-oriented programming. Swift is easy to grasp, which is good. If you are serious about becoming a great developer, it’s important to continue learning and expand your horizons.