Advanced Swift: Protocol Oriented Programming · Class Dispatch | raywenderlich.com


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1258671-advanced-swift-protocol-oriented-programming/lessons/2

Good video, just a few questions:

  • Are all methods dynamically dispatched by default in swift? (so there’s no need for the virtual keyword?)

  • Is the final keyword the only way to statically dispatch a method?

Hey, thanks for the question.

The default is everything is “virtual” for a class type in Swift. Try are correct that there is no virtual keyword. (Dispatch is static for structs and enums.)

The compiler can devirtualize with whole module optimization. If you have an internal method (to a module) the compiler can figure out that there are no overrides and do the devirtualize. You want to try to not use “final” as a tool for optimization. Rather, it is a single to clients that says that you do not want this method to be overridden. Optimization is a happy consequence.

There are other ways you can force static dispatch. If you declare a function static it will dispatch statically but also not be able to use self either. If you put a function as part of an extension it will also dispatch statically. (The trick here is you can give it full objective c dynamic dispatch with @objc if you need to). In other words, extensions do not let you add to the virtual table of a class in current Swift 5.

Thank you for your answer, I’ve just got a few more questions.

Why wouldn’t “final” be used to optimize? Because surely it would stop the compiler from having to check wether or not the method is overridden?

Also it seems a bit strange that methods in extensions dispatch statically by default, what is the reasoning behind that?

I think you are on the right track (my message might not have been that clear). Final certainly does make it easier for the optimizer to reason about devirtualization. I was trying to make the point that developers number one concern should be the usage semantics. Making everything final is fine if that is what you really intend.

That is the current limitation of the language. Just like with protocols, the formal protocol declares the customization points (virtual methods). Everything in extension is to either make a default implementation for an existing customization point or make a new statically dispatched method. It is the exact same story for classes but classes have the extra @objc trick not available to Swift protocols.

In general extension can be declared in other files and it would be challenging to modify the virtual table and witness tables after the fact.

Okay, thanks for the reply :slight_smile:

But what would happen if we declared a private extension of a class somewhere else and we added the @objc modifier?

  1. Would the class use message dispatch everywhere on the module
  2. Would modify its virtual table, appending the new method?
  3. Would use message dispatch only for the method in the extension and it’s virtual table everywhere else.

What I mean is the following:

File 1:

class BaseClass {
  final func methodZero() { }
  func methodOne() { }
}

extension BaseClass {
  func methodTwo() { }
}

class FirstClass {
  override methodOne() {}
}

File 2:

private extension BaseClass {
  @objc methodThree() {}
}

final class SecondClass {
      override methodOne() {}

      override methodThree() {}
}

Maybe the questions could be summarized as:

  1. how does access control affect dispatch?
  2. if more than two extensions that contained @objc methods for the same class were declared in independent files, would there only be one message dispatch “tables” configuration or would there be more?
  3. Explicitly declaring classes or methods final helps compilation time performance?
  1. Semantically the dispatch is always virtual (even though the compiler can devirtualize with whole module optimization and sufficiently restricted access). If the compiler can guarantee that the property or method can’t be overridden it may devirtualize and may even inline it.

  2. If you add an @objc function in an extension, when you call it, it will use the objective c runtime to dispatch the method. If you are going to do it from a subclass you will need to declare it open in the module the base class is defined.

  3. Generally I think that giving the compiler more information will speed up runtime and compile time. (Can’t say for sure though… the compiler might start inlining which could also have a subtle effect on compile time.) It is going to be relatively small however, so you need to balance that with what semantics (dynamism) you want to provide to your clients.

Hi rayfix,

Great Video Series. However i have a question

what did you declare func permiter() in extension ?

I think you are asking “why did I declare perimeter in an extension”

If it is declared as an extension and not part of the formal class definition, there is no way to override it from a derived class because it is not part of the vtable. BUT… but … if you declare it with @objc in the extension it uses full objective-C runtime message passing so you then can override it. That was the intended point.

@rayfix What I understood from this is Objective C and Swift use different mechanisms to achieve dynamic dispatch.Swift uses V table and Objective C uses a dispatch table. My doubt is how does swift improve the performance. Is there any underlying difference between swift and objective C in achieving the dynamic dispatch by checking respective tables and finding the right implementation?. When you say “swift supprorts objective C runtime also” that means in our example of an array containing different “Shape( Base class)” objets, dynamic dispatch happens in two different ways in Objective C and swift.

This is a really deep topic. To simplify drastically. There are 4 different kinds of dispatch that can happen in Swift:

  1. No dispatched (the code gets inlined)
  2. Static dispatch (just call the function)
  3. Vtable and Witness table lookup
  4. String based table lookup (objc)

The lower you go in the table, the more overhead you have. Objective C only uses 3. While this is the slowest it also give you great power. For example, you can go in and dynamically “swizzle” (replace) a method. Since Swift aggressively de-virutalizes calls that it knows it can, it gets its speed that way. On the other hand, swizzling in becomes impossible, so there is a cost to it.

Aside: If you have ever looked at how unit testing (XCTest) is implemented on macOS and Linux, Swift uses dynamic lookup to find all methods that begin with “test” and execute them. With Linux you had to explicitly list them. With the advent of metadata in Swift 5.1 it has apparently become possible to do some kind of runtime lookup of methods which is pretty exciting.