Reputation: 6475
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 class
es to fulfill requirement of implementing every property etc. but I see no possibility to achieve the same with structs
.
Upvotes: 96
Views: 27366
Reputation: 131
I have come up with a solution for this.
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
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"
You lose the required implementation error in the struct/class where you implement this protocol.
Upvotes: 3
Reputation: 179
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()
Upvotes: 12
Reputation: 2466
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()
}
}
Upvotes: 3
Reputation: 179
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
Upvotes: 4
Reputation: 13316
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.
Upvotes: 110
Reputation: 1365
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")
}
}
Upvotes: 10