Calling protocol default implementation from regular method

SwiftOopProtocolsSwift2

Swift Problem Overview


I'm wondering if it's possible to achieve such a thing.
I have a Playground like this:

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()

I can provide a default implementation in extension but what if Bar needs everything that is in default implementation plus additional things?
It's somehow similar to calling super. methods in classes to fulfill requirement of implementing every property etc. but I see no possibility to achieve the same with structs.

Swift Solutions


Solution 1 - Swift

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.

Solution 2 - Swift

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")
    }
}

Solution 3 - Swift

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

Solution 4 - Swift

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()

Solution 5 - Swift

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()
    }
}

Solution 6 - Swift

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncojojView Question on Stackoverflow
Solution 1 - SwiftAaron RasmussenView Answer on Stackoverflow
Solution 2 - SwiftThorsten KarrerView Answer on Stackoverflow
Solution 3 - SwiftJim MalakView Answer on Stackoverflow
Solution 4 - SwiftAmin MadaniView Answer on Stackoverflow
Solution 5 - SwiftDavid JamesView Answer on Stackoverflow
Solution 6 - SwiftAndi BeqiriView Answer on Stackoverflow