web-dev-qa-db-ja.com

Swiftサブクラスで実装されたメソッドの代わりにプロトコル拡張メソッドが呼び出されます

以下のコードで説明されている問題が発生しました(Swift 3.1)。

_protocol MyProtocol {
    func methodA()
    func methodB()
}

extension MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocol {

}

class SubClass: BaseClass {
    func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocol {
    func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// Default methodA
// JustClass methodA
_

したがって、私は"SubClass methodA"テキストがobject1.methodB()呼び出しの後に印刷されることを期待します。しかし、何らかの理由で、プロトコル拡張からのmethodA()のデフォルト実装が呼び出されます。ただし、object2.methodB() callは期待どおりに機能します。

別のSwiftプロトコルメソッドのディスパッチのバグですか、それとも何か不足していて、コードは正しく動作していますか?

20
Igor Kulagin

これは、プロトコルが現在メソッドをディスパッチする方法です。

プロトコルタイプのインスタンスで呼び出されたときにプロトコル要件の実装に動的にディスパッチするために、プロトコル監視テーブル(詳細は このWWDCトーク を参照)が使用されます。それはすべて、実際には、特定の適合タイプのプロトコルの各要件を呼び出すための関数実装のリストにすぎません。

プロトコルへの適合を示す各タイプは、独自のプロトコル監視テーブルを取得します。私は「適合を述べる」だけでなく、単に「適合」と述べたことに気づくでしょう。 BaseClassは、MyProtocolに準拠するための独自のプロトコル監視テーブルを取得します。ただし、SubClassMyProtocolに準拠するために独自のテーブルを取得しない代わりに、単にBaseClassに依存します。あなたが移動した場合
_: MyProtocol_ SubClassの定義まで、独自のPWTを持つことになります。

したがって、ここで考える必要があるのは、BaseClassのPWTがどのようなものかということだけです。まあ、それはプロトコル要件methodA()methodB()のどちらの実装も提供しないので、プロトコル拡張の実装に依存しています。つまり、BaseClassに準拠するMyProtocolのPWTには、拡張メソッドへのマッピングのみが含まれます。

したがって、拡張methodB()メソッドが呼び出され、methodA()への呼び出しが行われると、その呼び出しはPWTを介して動的にディスパッチされます(プロトコル型のインスタンスで呼び出されているため、つまりself)。したがって、これがSubClassインスタンスで発生した場合、BaseClassのPWTを実行します。したがって、SubClassがその実装を提供しているという事実に関係なく、methodA()の拡張実装を呼び出すことになります。

次に、JustClassのPWTについて考えてみましょう。 methodA()の実装を提供するため、MyProtocolに準拠するためのPWTには、methodA()のマッピングとしてthat実装があります。 methodB()の拡張実装も同様です。したがって、methodA()がそのPWTを介して動的にディスパッチされると、最終的にits実装になります。

私が言うように このQ&Aで 、サブクラスがスーパークラスに準拠しているプロトコルに対して独自のPWTを取得しないこの動作は、確かにいくらか驚くべきものであり、 バグとして報告されています 。 SwiftチームメンバーのJordan Roseがバグレポートのコメントで言っているように、その背後にある理由は

[...]サブクラスは、適合を満たすための新しいメンバーを提供することはできません。プロトコルは、あるモジュールの基本クラスと別のモジュールで作成されたサブクラスに追加できるため、重要です。

したがって、これが動作である場合、コンパイル済みのサブクラスには、別のモジュールでの事実の後に追加されたスーパークラスの適合性からのPWTが不足し、問題が発生します。


他の人がすでに言ったように、この場合の1つの解決策は、BaseClassmethodA()の独自の実装を提供させることです。このメソッドは、拡張メソッドではなく、BaseClassのPWTに含まれるようになります。

もちろん、ここではclassesを扱っているので、リストされているメソッドのBaseClassの実装だけではなく、代わりに- thunk 次に、クラスのvtable(クラスがポリモーフィズムを実現するメカニズム)を介して動的にディスパッチします。したがって、SubClassインスタンスの場合、methodA()のオーバーライドを呼び出すことになります。

29
Hamish

あなたのコードでは、

let object1 = SubClass()
object1.methodB()

SubClassのインスタンスからmethodBを呼び出しましたが、SubClassにはmethodBという名前のメソッドがありません。ただし、そのスーパークラスBaseClassは、MyProtocol methodBを持つmethodBに準拠しています。

したがって、methodBからMyProtocalを呼び出します。したがって、extesion MyProtocolmethodAを実行します。

期待どおりに到達するには、次のコードのように、methodABaseClassを実装し、SubClassでオーバーライドする必要があります。

class BaseClass: MyProtocol {
    func methodA() {
        print("BaseClass methodA")
    }
}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}

今、出力は次のようになります

//Output
//SubClass methodA
//JustClass methodA

メソッドはあなたが期待するものに到達することができますが、この種のコード構造体が推奨されるかどうかはわかりません.

0
mrfour

まあ私はサブクラスメソッドAがポリモーフィックでないと思います。クラスがメソッドがプロトコルの拡張機能で実装されていることをクラスが認識していないため、オーバーライドできないためです。拡張メソッドは、Objective Cで未定義の動作で2つの正確なカテゴリメソッドが互いに切り替わるように、実行時に実装を踏んでいる可能性があります。この動作を修正するには、モデルに別のレイヤーを追加し、クラスではなくクラスにメソッドを実装します。プロトコル拡張、したがって、それらからポリモーフィックな動作を取得します。欠点は、抽象クラスのネイティブサポートがないため、この層にメソッドを実装しないでおくことができないことです(これは、実際にはプロトコル拡張で実行しようとしていることです)。

protocol MyProtocol {
    func methodA()
    func methodB()
}

class MyProtocolClass: MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocolClass {

}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocolClass {
    override func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// SubClass methodA
// JustClass methodA

ここでも関連する回答: Swift Protocol Extensions overriding

0
Fernando Mazzon