Reasons to include function in protocol definition vs. only defining it in the extension?

Declaring the function as part of the protocol definition instructs the compiler to use dynamic dispatch when calling the function, as the compiler would expect types implementing the protocol to give an implementation for that function. This is called a method requirement. Now, if the type doesn’t define the method, then the runtime resolves the method call to the method declared in the protocol extension.

However, declaring the function in the protocol extension only tells the compiler that he doesn’t need to use the dynamic dispatch, and instead it uses the static dispatch, which is faster, but doesn’t work very well with polymorphism, as the protocol extension implementation will be called even if the types conforming to the protocol also implement the method.

To exemplify the above, let’s consider the following code:

protocol Shape {
    func draw()
}

extension Shape {
    func draw(){
        print("This is a Shape")
    }
}

struct Circle: Shape {
    func draw() {
        print("This is a Circle")
    }
}

struct Square: Shape {
    func draw() {
        print("This is a Square")
    }
}

let shapes: [Shape] = [Circle(), Square()]

for shape in shapes {
    shape.draw()
}

The above code will have the output

This is a Circle 
This is a Square

This is because draw() is a method requirement, meaning that when draw() is invoked, the runtime will search for the implementation of draw () within the actual type of the element, in this case within Circle and Square.

Now if we don’t declare draw as a method requirement, meaning we don’t mention it within the protocol declaration

protocol Shape {
}

Then the compiler will no longer use the dynamic dispatch, and will go straight to the implementation defined in the protocol extension. Thus the code will print:

This is a Shape
This is a Shape

More, if we down cast cast an element of the array to the type we expect it would be, then we get the overloaded behaviour. This will print This is a Circle

if let circle = shapes[0] as? Circle {
    circle.draw()
}

because the compiler is now able to tell that the first element of shapes is a Circle, and since Circle has a draw() method it will call that one.

This is Swift’s way to cope with abstract classes: it gives you a way you to specify what you expect from types conforming to that protocol, while allowing for default implementations of those methods.

Leave a Comment