以下のコードで説明されている問題が発生しました (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
だから私は「サブクラスメソッドA」呼び出し後にテキストが印刷されるはずですobject1.methodB()
。しかし、何らかの理由で、methodA()
プロトコル拡張からのデフォルトの実装が呼び出されます。ただし、object2.methodB()
呼び出しは期待どおりに動作します。
これはプロトコル メソッド ディスパッチにおける別の Swift のバグでしょうか、それとも何かが欠けていてコードは正しく動作しているのでしょうか?
ベストアンサー1
これは、プロトコルが現在メソッドをディスパッチする方法です。
プロトコル証人テーブル(このWWDCトーク詳細については、プロトコル型インスタンスで呼び出されたときに、プロトコル要件の実装に動的にディスパッチするために使用されます。これは、特定の準拠型のプロトコルの各要件に対して呼び出す関数実装のリストにすぎません。
プロトコルへの適合を宣言する各タイプは、独自のプロトコル証人テーブルを取得します。ここで「適合を宣言する」と述べており、「に準拠する」だけではないことに注意します。は、適合BaseClass
に関する独自のプロトコル証人テーブルを取得しますMyProtocol
。ただし、SubClass
ないは への準拠に関する独自のテーブルを取得しますMyProtocol
が、代わりに は の だけに依存しますBaseClass
。
: MyProtocol
を の定義まで下げるとSubClass
、 は独自の PWT を持つようになります。
したがって、ここで考えなければならないのは、 の PWT がどのBaseClass
ようなものかということです。 は、プロトコル要件のいずれの実装も提供していないmethodA()
ためmethodB()
、プロトコル拡張の実装に依存しています。つまり、BaseClass
に準拠するPWT にはMyProtocol
、拡張メソッドへのマッピングのみが含まれているということです。
したがって、拡張methodB()
メソッドが呼び出され、 が を呼び出すとmethodA()
、その呼び出しは PWT を通じて動的にディスパッチされます (プロトコル型インスタンス、つまり で呼び出されるためself
)。したがって、これがSubClass
インスタンスで発生すると、 の PWT が処理されます。したがって、 が の実装を提供しているという事実に関係なく、BaseClass
の拡張実装が呼び出されることになります。methodA()
SubClass
さて、 の PWT を考えてみましょうJustClass
。 は の実装を提供するので、へmethodA()
の適合性に関する PWT はMyProtocol
それのマッピングとしての実装methodA()
、および の拡張実装methodB()
。したがって、methodA()
が PWT を介して動的にディスパッチされる場合、次のようになります。その実装。
私が言うようにこのQ&Aでサブクラスがスーパークラスが準拠するプロトコルに対して独自のPWTを取得しないというこの動作は確かに驚くべきものであり、バグとして報告SwiftチームのメンバーであるJordan Roseがバグレポートのコメントで述べているように、その理由は
[...] サブクラスは、適合性を満たすために新しいメンバーを提供することはできません。これは、あるモジュールの基本クラスにプロトコルを追加し、別のモジュールにサブクラスを作成できるため重要です。
したがって、この動作の場合、すでにコンパイルされたサブクラスには、別のモジュールで事後に追加されたスーパークラスの適合からの PWT が欠落し、問題が発生します。
BaseClass
他の人が既に述べているように、この場合の 1 つの解決策は、 が の独自の実装を提供することです。このメソッドは、拡張メソッドではなく、 の PWTmethodA()
内にあります。BaseClass
もちろん、私たちが扱っているのはクラスBaseClass
ここでは、単にリストされているメソッドの実装ではなく、ドンその後、クラスの vtable (クラスがポリモーフィズムを実現するメカニズム) を通じて動的にディスパッチされます。したがってSubClass
、たとえば、 のオーバーライドを呼び出すことになりますmethodA()
。