从常规方法调用协议默认实现

我想知道是否有可能做到这一点。
我有一个这样的游乐场:

protocol Foo {
func testPrint()
}


extension Foo {
func testPrint() {
print("Protocol extension call")
}
}


struct Bar: Foo {
func testPrint() {
// Calling self or super go call default implementation
self.testPrint()
print("Call from struct")
}
}




let sth = Bar()
sth.testPrint()

我可以在 extension中提供一个默认实现,但是如果 Bar需要默认实现中的所有内容以及其他内容,那该怎么办?
这有点类似于在 classes 中调用 super.方法来满足实现每个属性等等的要求,但是我看不到在 structs中实现同样的要求的可能性。

23358 次浏览

Well, you could create a nested type conforming to the protocol, instantiate it, and call the method on that one (it does not matter that you cannot access your type's data as the implementation inside the protocol extension cannot reference it anyway). But it's not a solution I'd call elegant.

struct Bar: Foo {
func testPrint() {
// Calling default implementation
struct Dummy : Foo {}
let dummy = Dummy()
dummy.testPrint()
print("Call from struct")
}
}

I don't know if you are still looking for an answer to this, but the way to do it is to remove the function from the protocol definition, cast your object to Foo and then call the method on it:

protocol Foo {
// func testPrint() <- comment this out or remove it
}


extension Foo {
func testPrint() {
print("Protocol extension call")
}
}


struct Bar: Foo {
func testPrint() {
print("Call from struct")
(self as Foo).testPrint() // <- cast to Foo and you'll get the  default
//    function defined in the extension
}
}


Bar().testPrint()


// Output:    "Call from struct"
//            "Protocol extension call"

For some reason it only works if the function isn't declared as part of the protocol, but is defined in an extension to the protocol. Go figure. But it does work.

Thanks for the post! If you put the function definition in the protocol then when the object is casted as the protocol it only sees the object's version of the function and since you are calling it inside itself you get the new address of Apple ...

I did try a version like this:

import UIKit
protocol MyProc
{
}


protocol MyFuncProc
{
func myFunc()
}


extension MyProc
{
func myFunc()
{
print("Extension Version")
}
}


struct MyStruct: MyProc, MyFuncProc
{
func myFunc()
{
print("Structure Version")
(self as MyProc).myFunc()
}
}


(MyStruct() as MyFuncProc).myFunc()

This gives an output of:

Structure Version
Extension Version

In case your protocol has associatedType or Self requirements, then the cast will not work. To work around this, create a "shadow" default implementation that both the regular default implementation and the conforming type can call.

protocol Foo {
associatedType Bar
}


extension Foo {
func testPrint() {
defaultTestPrint()
}
}


fileprivate extension Foo { // keep this as private as possible
func defaultTestPrint() {
// default implementation
}
}


struct Bar: Foo {
func testPrint() {
// specialized implementation
defaultTestPrint()
}
}

what do you think about such way of fixing this ?

protocol Foo {
func testPrint()
}


extension Foo {
func testPrint() {
defaultTestPrint()
}


func defaultTestPrint() {
print("Protocol extension call")
}
}


struct Bar: Foo {
func testPrint() {
// Calling self or super go call default implementation
defaultTestPrint()
print("Call from struct")
}
}




let sth = Bar()
sth.testPrint()

I have come up with a solution for this.

Issue

When you have a default implementation in an extension, when you implement the protocol to another class/struct, you lose this default implementation if you implement the method. This is by design, this is how protocols work

Solution

  • Create a Default Implementation of your protocol and make it a property of your protocol.
  • Then when you implement this protocol in a class, provide your default implementation with a getter
  • Call default implementation when you need to.

Example


protocol Foo {
var defaultImplementation: DefaultImpl? { get }
func testPrint()
}


extension Foo {
// Add default implementation
var defaultImplementation: DefaultImpl? {
get {
return nil
}
}
}


struct DefaultImpl: Foo {
func testPrint() {
print("Foo")
}
}




extension Foo {
    

func testPrint() {
defaultImplementation?.testPrint()
}
}


struct Bar: Foo {
    

var defaultImplementation: DefaultImpl? {
get { return DefaultImpl() }
}
func testPrint() {
if someCondition {
defaultImplementation?.testPrint() // Prints "Foo"
}
}
}


struct Baz: Foo {
func testPrint() {
print("Baz")
}
}




let bar = Bar()
bar.testPrint() // prints "Foo"


let baz = Baz()
baz.testPrint() // prints "Baz"




Drawbacks

You lose the required implementation error in the struct/class where you implement this protocol.