Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Take the following protocol and extension:

protocol ProtocolA {
    func myFunc()
}

extension ProtocolA {
    func myFunc() {
        print("Default ProtocolA implementation.")
    }
}

What is the difference, if any, between that and leaving the function out of the protocol definition entirely, like this:

protocol ProtocolB { }

extension ProtocolB {
    func myFunc() {
        print("Default ProtocolB implementation.")
    }
}

I found one difference. If I define a struct that overrides the default implementation, I can only cast it to the protocol and call the protocol's implementation if I leave the function out of the definition:

struct A: ProtocolA {
    func myFunc() {
        print("Struct A's implementation.")
    }
}

struct B: ProtocolB {
    func myFunc() {
        print("Struct B's implementation.")
    }
}

A().myFunc()                   // "Struct A's implementation."
(A() as ProtocolA).myFunc()    // "Struct A's implementation."

B().myFunc()                   // "Struct B's implementation."
(B() as ProtocolB).myFunc()    // "Default protocol implementation."

In other words, if you take the function out of the protocol definition like in ProtocolB then you can access the default implementation by casting the object to the protocol. On the other hand, if you leave the function in the protocol definition, then you can't cast to the protocol in order to get the default protocol behavior.

Leaving the function definition out of the protocol seems to allow for the most flexibility in terms of behavior.

What is the downside? What do you lose if you take the function out of the protocol definition? Do you lose any functionality at all?

like image 662
Aaron Rasmussen Avatar asked Jan 13 '16 22:01

Aaron Rasmussen


People also ask

What are the protocols What is protocol extension?

Protocols let you describe what methods something should have, but don't provide the code inside. Extensions let you provide the code inside your methods, but only affect one data type – you can't add the method to lots of types at the same time.

What is protocol composition and protocol extension?

Protocol composition is the process of combining multiple protocols into a single protocol. You can think of it as multiple inheritance. With protocol DoSomething , below, class HasSomethingToDo is able to do whatShouldBeDone and anotherThingToBeDone .

Which keyword do you use to define a protocol?

If you define a protocol instance method requirement that's intended to mutate instances of any type that adopts the protocol, mark the method with the mutating keyword as part of the protocol's definition. This enables structures and enumerations to adopt the protocol and satisfy that method requirement.

What is protocol and how do you implement it?

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements.


1 Answers

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.

like image 78
Cristik Avatar answered Sep 20 '22 20:09

Cristik