Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
463 views
in Technique[技术] by (71.8m points)

swift - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...