以下のコードで説明されている問題が発生しました(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プロトコルメソッドのディスパッチのバグですか、それとも何か不足していて、コードは正しく動作していますか?
これは、プロトコルが現在メソッドをディスパッチする方法です。
プロトコルタイプのインスタンスで呼び出されたときにプロトコル要件の実装に動的にディスパッチするために、プロトコル監視テーブル(詳細は このWWDCトーク を参照)が使用されます。それはすべて、実際には、特定の適合タイプのプロトコルの各要件を呼び出すための関数実装のリストにすぎません。
プロトコルへの適合を示す各タイプは、独自のプロトコル監視テーブルを取得します。私は「適合を述べる」だけでなく、単に「適合」と述べたことに気づくでしょう。 BaseClass
は、MyProtocol
に準拠するための独自のプロトコル監視テーブルを取得します。ただし、SubClass
はMyProtocol
に準拠するために独自のテーブルを取得しない代わりに、単に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つの解決策は、BaseClass
にmethodA()
の独自の実装を提供させることです。このメソッドは、拡張メソッドではなく、BaseClass
のPWTに含まれるようになります。
もちろん、ここではclassesを扱っているので、リストされているメソッドのBaseClass
の実装だけではなく、代わりに- thunk 次に、クラスのvtable(クラスがポリモーフィズムを実現するメカニズム)を介して動的にディスパッチします。したがって、SubClass
インスタンスの場合、methodA()
のオーバーライドを呼び出すことになります。
あなたのコードでは、
let object1 = SubClass()
object1.methodB()
SubClass
のインスタンスからmethodBを呼び出しましたが、SubClass
にはmethodB
という名前のメソッドがありません。ただし、そのスーパークラスBaseClass
は、MyProtocol
methodBを持つmethodB
に準拠しています。
したがって、methodB
からMyProtocal
を呼び出します。したがって、extesion MyProtocol
のmethodA
を実行します。
期待どおりに到達するには、次のコードのように、methodA
にBaseClass
を実装し、SubClass
でオーバーライドする必要があります。
class BaseClass: MyProtocol {
func methodA() {
print("BaseClass methodA")
}
}
class SubClass: BaseClass {
override func methodA() {
print("SubClass methodA")
}
}
今、出力は次のようになります
//Output
//SubClass methodA
//JustClass methodA
メソッドはあなたが期待するものに到達することができますが、この種のコード構造体が推奨されるかどうかはわかりません.
まあ私はサブクラスメソッド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