译 文:Static vs Dynamic Dispatch in Swift: A royal choice

Author: Shubham Bakshi

There are three related articles in this paper, all of which have been translated. I hope it will be helpful for your learning of Swift

[a] dispatch [b] dispatch [C] dispatch [D] dispatch Message-dispatch system (message-dispatch system

Like the comment and hope it gets better and better with your help

Author: @ios growth refers to north, this article was first published in the public number iOS growth refers to north, welcome to the correction

Please contact me if there is a need to reprint, remember to contact oh!!

Well, if you’re a fan of OOP, these distribution methods, especially dynamic distribution, are probably familiar to you.

Method dispatch is a mechanism that helps determine which operation should be performed, or more specifically, which method implementation should be used.

The basic information

First, both value types and reference types support static dispatch.

However, dynamic dispatch is supported for reference-only types (that is, classes). The reason for this is that, in a nutshell, for dynamic or dynamic distribution, we need inheritance, and our value type does not support inheritance.

With that in mind, let’s move on!

For a fuller understanding, there are not just two (static and dynamic) dispatch methods in Swift, but four —

  1. Inline inline (fastest)
  2. Static distributed
  3. Distributed virtual
  4. Dynamic distribution (slowest)

It is up to the compiler to decide which distribution method should be used, using inline first and then choosing on demand.

Static vs dynamicSwift vs Objective-C

By default, Objective-C supports dynamic distribution. This distribution technique gives developers flexibility in the form of polymorphism! Subclassing and overwriting existing methods and things is great to use. But there is a price.

Dynamic dispatch improves the expressiveness of the language at the cost of constant runtime overhead. This means that, in the case of dynamic dispatch, for each method call, our compiler must look at the Witness Table (virtual table or party publication in other languages) that we are calling to check the implementation of a particular method. The compiler needs to determine whether you are referring to a superclass implementation or a subclass implementation. Since memory for all objects is allocated at run time, the compiler can only perform this check at run time.

Static distribution, however, does not have this problem. With this distribution technique, the compiler already knows at compile time which method implementation a method will call. As a result, the compiler can perform certain optimizations and even convert code to inline if possible, making overall execution faster!

So, how to achieve the above two distributions in Swift?

  • To implement dynamic dispatch, we use inheritance, subclass the base class, and then override the existing methods of the base class. In addition, we can usedynamicKeyword, and needs to be preceded by@objcKeyword to expose our method toObjective – C runtime
  • To implement static distribution, we need to usefinalstaticKeyword, because both ensure that classes and methods cannot becover.

Let’s dig deeper

Statically distributed (or directly invoked)

As mentioned above, they are very fast compared to dynamic dispatch because the compiler is able to locate instructions at compile time. Therefore, when a function is called, the compiler jumps directly to the function’s memory address to perform the operation. This leads to huge performance gains and certain compiler optimizations, such as inlining.

Dynamic dispatch

As mentioned earlier, in this type of distribution, the implementation is chosen at run time rather than compile time, which adds some overhead.

Now, you may be wondering why we use it, or even have to use it, since it’s so expensive!

Well, because of its flexibility. In fact, most OOP languages support dynamic dispatch because it allows polymorphism.

There are two types of dynamic distribution

  1. Provided by the Table(Form distributed)

This distribution technique utilizes a table, a set of function Pointers called witness Tables (or virtual tables), to find implementations of specific methods.

So how does the Witness Table work?

  • Each subclass has its own copy of this table
  • The table has a different function pointer for each method overridden like this
  • When subclasses add new methods, these method Pointers are appended to the end of this array
  • Finally, the compiler uses this table at run time to see which implementation to call for the method

Because the compiler has to read the memory address of the method implementation from the table and then jump to that address, it needs to execute two additional instructions, so it is slower than static dispatch, but still faster than Message dispatch.

** Note: ** I am not sure, but this particular distribution technique can be virtual distribution, although it uses virtual tables, BUT I can’t find a specific reference.

  1. The Message sent(Message distribution)

This dynamic distribution technology is currently the most dynamic (pun intended). In fact, it is so good (leaving out the optimization part) that the Cocoa framework uses it in many of its larger frameworks (e.g. KVO, Core Data, etc.).

In addition, it supports method swizzling, which usually means that using this technique, we can change the functionality of a method at run time.

However, the Swift compiler does not provide this off-the-shelf functionality. Instead, this distribution technique is implemented using the Objective-C runtime.

To explicitly use this distribution method, we need to use the dynamic keyword. Before Swift 4.0, we implicitly added @objc whenever we used Dynamic, but starting with Swift 4.0, we needed to flag it explicitly with @objc to expose our methods to the Objective-C runtime for messaging.

Because we are using the Objective-C runtime, when sending a message, the runtime will grab the class hierarchy to determine which method to call. It’s really slow. To compensate for its performance, it provides a cache, which is different to some extent.

Unless we display the dynamic keyword, the compiler will always try to upgrade the dispatch method to static dispatch.

The sample

Value types

struct Person {
    func isIrritating(a) -> Bool{}// Static
}
extension Person {
    func canBeEasilyPissedOff(a) -> Bool{}// Static
}
Copy the code

Because structs and enums are value types and do not support inheritance, the compiler places methods statically distributed because it knows they will never be subclassed.

Protocol

protocol Animal {
    func isCute(a) -> Bool{}// Table
}
extension Animal {
    func canGetAngry(a) -> Bool{}// Static
}
Copy the code

The key point to note here is that any method defined in the extension uses static dispatch

Class

class Dog: Animal {
    func isCute(a) -> Bool{}// Table
    @objc dynamic func hoursSleep(a) -> Int{}// Message
}
extension Dog {
    func canBite(a) -> Bool{}// Static
    @objc func goWild(a){}// Message
}
final class Employee {
    func canCode(a) -> Bool{}// Static 
}
Copy the code
  • Normal method declarations follow the same principles as protocols
  • Whenever we use@objcObjective-CWhen a runtime display declares a method, the method uses messaging
  • However, if we mark a class asfinal, the class cannot be subclassed, so its methods use static distribution.

You can refer to the table below for comparison

Direct call Table Message
Clear implementation final.static dynamic
Value types All the methods
agreement Methods in development Defined methods
class Methods in development Defined methods with@objcThe extension of the

Okay, you’re gonna believe everything I say, right?

So how do you prove that these methods are actually calling the corresponding distributive technology as I said?

To this end, we must understand Swift Intermediate Language (SIL)[1]. Through my research on the Internet, I found that there is a way —

  1. If the function is dispatched using a table, it appears in the VTABLE (or witness_table)

    sil_vtable Animal{#Animal.isCute!1: (Animal) -> () -> () : main.Animal.isCute() -> () // Animal.isCute()
    .
    }s
    
    Copy the code
  2. If a function/method uses message dispatch, the SIL will have the volatile keyword in the call. In addition, you’ll find two tags foreign and objc_method, indicating that the function/method is called using the Objective-C runtime.

    %14 = class_method [volatile] %13 : $Dog#,Dog.goWild!1.foreign : (Dog) -> () -> (), $@convention(objc_method) (Dog) - > ()Copy the code
  3. If neither of the above conditions occurs, then the function/method uses static scheduling.

Well, that’s all for this article! I plan this to be a two-part series, and the next article (now available here [2]) will cover performance comparisons between static and dynamic scheduling through test cases.

Content inspiration: Method distribution in Swift [3] — Thuyen’s Corner.

You can connect with the original author on LinkedIn[4] and through other channels [5].

The resources

Swift Intermediate Language (SIL) : github.com/apple/swift…

The Static Dispatch Over the Dynamic Dispatch: betterprogramming. Pub/Static – disp…

Method dispatch in Swift: trinhngocthuyen. Making. IO/tech/Method…

LinkedIn:www.linkedin.com/in/shubhamb…

Other channels: About.me/Shubhambaks…


If you have any questions, please comment directly, and feel free to express anything wrong with the article. If you wish, you can spread the word by sharing this article.

Thank you for reading this! 🚀