I want to have a solid understanding of method dispatching in Swift. I read from this popular blog about the three types of dispatches as below:
In that blog the author says that the NSObject subtypes maintain a dispatch table(witness table) and also a message dispatch hierarchy. The code snippet shared by the author is as below:
class Person: NSObject {
func sayHi() {
print("Hello")
}
}
func greetings(person: Person) {
person.sayHi()
}
greetings(person: Person()) // prints 'Hello'
class MisunderstoodPerson: Person {}
extension MisunderstoodPerson {
override func sayHi() {
print("No one gets me.")
}
}
greetings(person: MisunderstoodPerson()) // prints 'Hello'
I will quote the author's reasoning for the call sayHi() on an instance of Person:
The greetings(person:) method uses table dispatch to invoke sayHi(). This resolves as expected, and “Hello” is printed. Nothing too exciting here. Now, let’s subclass Person
The the author goes on to explain the call sayHi() on an instance of MisunderstoodPerson typecast to Person:
Notice that sayHi() is declared in an extension meaning that the method will be invoked with message dispatch. When greetings(person:) is invoked, sayHi()is dispatched to the Person object via table dispatch. Since the MisunderstoodPerson override was added via message dispatch, the dispatch table for MisunderstoodPerson still has the Person implementation in the dispatch table, and confusion ensues.
I want to know how the author reached to the conclusion that the greetings(person:) method uses table dispatch to invoke sayHi()
One thing that author mentions earlier in the blog is when an NSObject subclass declares a method in the initial declaration(meaning not in extension) a table dispatch would be used.
So I assume that as the parameter 'person' type is Person for the greetings(person:) method and the method called is sayHi() which is declared in the initial declaration of the Person class a table dispatch is used and sayHi() from Person is called. Its safe to say Person witness table is used.
Once we have subclass MisunderstoodPerson and pass this instance to greetings(person:) this instance should be treated as Person. I have a confusion here and have couple of questions here.
The author does not clarify this in the blog as to whose witness tables should be used. Even in some cases I get a feel that author describes even compilers creating witness tables for protocols. It is evident from the image he shares in the blog which is as below.
I would be really grateful, If some one could explain it.
That blog post is a bit outdated in that inheriting from NSObject
no longer changes the dispatch behaviour of a class (in Swift 3, it would cause members to be implicitly exposed to Obj-C, which would change dispatch behaviour of extension members, but this is no longer the case). The example they give also no longer compiles in Swift 5, as you can only override a dynamic
member from an extension.
In order to distinguish static dispatch from dynamic dispatch, let's consider protocols separately. For protocols, dynamic dispatch is used if both:
The member is called on a protocol-typed value P
, protocol composition typed value P & X
, or generic placeholder typed value T : P
, for example:
protocol P {
func foo()
}
struct S : P {
func foo() {}
}
func bar(_ x: S) {
x.foo() // Statically dispatched.
}
func baz(_ x: P) {
x.foo() // Dynamically dispatched.
}
func qux<T : P>(_ x: T) {
x.foo() // Also dynamically dispatched.
}
If the protocol is @objc
, message dispatch is used, otherwise table dispatch is used.
For non-protocol members, you can ask the question: "Can this be overriden?". If the answer is no, you're looking at static dispatch (e.g a struct
member or final
class member). If it can be overriden, you're looking at some form of dynamic dispatch. However it's worth noting that if the optimiser can prove that it isn't overriden (e.g if it's fileprivate
and not overriden within that file), then it can be optimised to use static dispatch.
For a normal method call, the dynamic
modifier is what distinguishes the two current forms of dynamic dispatch, table dispatch and Obj-C message dispatch. If a member is dynamic
, Swift will use message dispatch. As stated, this rule seems pretty straightforward, however some members are not explicitly marked dynamic
– the compiler infers it instead. This includes:
dynamic
members.NSManaged
properties.@objc
class extension members.A lesser known form of method call in Swift is the dynamic method call, which is done by accessing an @objc
member on an AnyObject
value. For example:
import Foundation
class C {
@objc func foo() {}
}
func bar(_ x: AnyObject) {
// Message dispatch (crashing if the object doesn't respond to foo:).
x.foo!()
}
Such calls always use message dispatch.
And I think that about summarises the current rules around which dispatch mechanisms are used where.
Once we have subclass
MisunderstoodPerson
and pass this instance togreetings(person:)
this instance should be treated asPerson
. I have a confusion here and have couple of questions here.
- The instance is of type
MisunderstoodPerson
so would witness table ofMisunderstoodPerson
be used here.- Or the instance has been typecast to
Person
, so would witness table ofPerson
be used here.
(slight terminology nitpick: for classes, it's called a vtable rather than a witness table)
It's always the vtable that corresponds to the dynamic type of the instance that's used, so in this case it would be MisunderstoodPerson
's vtable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With