通常のメソッドからプロトコルのデフォルト実装を呼び出す


83

そのようなことを成し遂げることは可能かと思います。
私はこのような遊び場を持っています:

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はどうなりますか?すべてのプロパティなどを実装するという要件を満たすためにesでメソッド
を呼び出すのとどういうわけか似ていますが、で同じことを達成する可能性はありません。super.classstructs


私が使用しますFoo.testPrint(self)()-問題は、セグメンテーション違反が原因で失敗することです(7.0 GMと7.1ベータの両方でテスト済み)
Antonio

1
それはあなたが😯提示してきた奇妙な工事だ
cojoj

4
すべてのインスタンスメソッドは、インスタンスを最初のパラメーターとして使用する静的カレーメソッドです
Antonio

ただし、拡張機能を削除しようとすると、同じセグメンテーションエラーがスローされます。おそらくそれはプロトコルでは機能しないはずです
アントニオ

うーん、デフォルトの実装を使用して簡単に修正できるのに、コードで自分自身を繰り返す必要があるのは残念です...
cojoj 2015

回答:


90

あなたがまだこれに対する答えを探しているかどうかはわかりませんが、それを行う方法は、プロトコル定義から関数を削除し、オブジェクトをにキャストしてからFoo、そのメソッドを呼び出すことです。

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"

何らかの理由で、関数がプロトコルの一部として宣言されていない場合にのみ機能しますが、プロトコルの拡張で定義されています。図に行きます。しかし、それは機能します。


1
なんてこった!彼らがあなたにプロトコルから機能を取り除くことを強制するなんて信じられない!良い答え、ありがとう!
SkyWalker 2016年

5
これは私にはバグのように見えます。同じインスタンスをキャストするだけでは、異なるメソッドの実装を取得することはできないはずです。
MANIAK_dobrii 2016

15
これにより、プロトコル+拡張機能のセマンティクスが実際に大幅に変更されます。宣言をプロトコルから除外すると、プロトコルに準拠する型で関数を呼び出すときに静的ディスパッチが発生します。これが、拡張機能から実装をキャストして取得できる理由です。プロトコルに宣言を追加すると、関数の呼び出しが動的にディスパッチされます。
Thorsten Karrer

2
ただし、これは、Fooプロトコルが他のプロトコルから継承しない場合にのみ機能します。
iyuna 2017

4
これは無限ループにつながりませんか?
stan liu 2018

9

さて、プロトコルに準拠するネストされた型を作成し、それをインスタンス化し、その型のメソッドを呼び出すことができます(プロトコル拡張内の実装はとにかくそれを参照できないため、型のデータにアクセスできないことは問題ではありません)。しかし、それは私がエレガントと呼ぶ解決策ではありません。

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

1
それは👌それが(Appleが確認された)今、唯一の可能性だように、それは有用である可能性があるとして...私は、このいずれかの機能レーダーを提出うに見える
cojoj

4

投稿ありがとうございます!関数定義をプロトコルに入れると、オブジェクトがプロトコルとしてキャストされると、オブジェクトの関数のバージョンのみが表示され、それ自体の内部で呼び出すため、Appleの新しいアドレスを取得します...

私はこのようなバージョンを試しました:

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

これにより、次の出力が得られます。

Structure Version
Extension Version

3

あなたのプロトコルが持っている場合associatedTypeSelf要件、そしてキャストは動作しません。これを回避するには、通常のデフォルト実装と準拠タイプの両方が呼び出すことができる「シャドウ」デフォルト実装を作成します。

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

これ以上あなたに同意することはできません。defaultXX()他の答えよりもはるかに表現力があり、読みやすいです。
DawnSong

そして、アミン・マダニがあなたの答えを改善したと思います。
DawnSong

2

これを修正するそのような方法についてどう思いますか?

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

これは答えですか?
アンファム

@AnhPhamのthatsそれは、ちょうど別の方法は、デフォルトの機能を実行する
Aminのマダニ

包丁と簡単な解決策
StephanJanuar19年

最も表現力豊かで柔軟な答え。
DawnSong

2

私はこれに対する解決策を考え出しました。

問題

拡張機能にデフォルトの実装がある場合、プロトコルを別のクラス/構造体に実装すると、メソッドを実装するとこのデフォルトの実装が失われます。これは仕様によるもので、これがプロトコルの仕組みです

解決

  • プロトコルのデフォルト実装を作成し、それをプロトコルのプロパティにします。
  • 次に、このプロトコルをクラスに実装するときに、デフォルトの実装にゲッターを提供します
  • 必要に応じて、デフォルトの実装を呼び出します。


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"


欠点

このプロトコルを実装する構造体/クラスで必要な実装エラーが失われます。


1
コードが機能する理由を必ず説明してください。これはあなたの答えを訪問する他の人がそれから学ぶのを助けます。
AlexH

ええ、それは良い実装です!
juxhinbleta20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.