post Image
Swiftで継承とプロトコルとprotocol extensionが絡んだ時のメソッド呼び出し

Swiftで継承とプロトコルとprotocol extensionが絡んだ時に呼び出されるメソッドがわかりづらい事があるのでまとめました。

バージョン

$ swift --version
Apple Swift version 4.2 (swiftlang-1000.0.36 clang-1000.10.44)
Target: x86_64-apple-darwin17.7.0

ソース

protocol AnimalProtocol {
    func a()
    func c()
    func d()
}

extension AnimalProtocol {
    func a() {
        print("AnimalProtocol.a")
    }

    func b() {
        print("AnimalProtocol.b")
    }

    func c() {
        print("AnimalProtocol.c")
    }

    func d() {
        defaultD(self)
    }
}

func defaultD<X: AnimalProtocol>(_ obj: X) {
    print("AnimalProtocol.d")
}

class Animal : AnimalProtocol {
    func a() {
        print("Animal.a")
    }

    func b() {
        print("Animal.b")
    }

    func d() {
        defaultD(self)
    }
}

class Cat : Animal {
    override func a() {
        print("Cat.a")
    }

    override func b() {
        print("Cat.b")
    }

    func c() {
        print("Cat.c")
    }

    override func d() {
        print("Cat.d")
    }
}

func invokeA<X: AnimalProtocol>(_ x: X) {
    x.a()
}
func invokeB<X: AnimalProtocol>(_ x: X) {
    x.b()
}
func invokeC<X: AnimalProtocol>(_ x: X) {
    x.c()
}
func invokeD<X: AnimalProtocol>(_ x: X) {
    x.d()
}

let cat: Cat = Cat()
invokeA(cat) // => Cat.a
invokeB(cat) // => AnimalProtocol.b
invokeC(cat) // => AnimalProtocol.c
invokeD(cat) // => Cat.d

解説

上記のコードは、AnimalProtocolというプロトコル、Animalという親クラス、 Catというサブクラスにおいて、メソッドをどのように実装するかによって、挙動がどう異なるかを示したものです。
パターンはA, B, C, Dの4種類あります。
あるCat型の式がある時、それに対するメソッド呼び出しは、Catで定義されたメソッドが呼ばれます。しかし、このコードのように<X: AnimalProtocol>というジェネリックな型に対して呼び出した場合は、Catで定義されたものが呼ばれる場合と、そうでない場合があります。

パターンA

このパターンは以下のとおりです。

  • プロトコル定義にメソッド定義が ある
  • (共通)protocol extensionに実装が ある
  • 親クラスに実装が ある
  • サブクラスにoverride func実装が ある

この場合、サブクラスの実装が呼ばれます。

パターンB

このパターンは以下のとおりです。

  • プロトコル定義にメソッド定義が ない
  • (共通)protocol extensionに実装が ある
  • 親クラスに実装が ある
  • サブクラスにoverride func実装が ある

この場合、protocol extensionの実装が呼ばれます。

パターンC

このパターンは以下のとおりです。

  • プロトコル定義にメソッド定義が ある
  • (共通)protocol extensionに実装が ある
  • 親クラスに実装が ない
  • サブクラスにfunc実装が ある

この場合、protocol extensionの実装が呼ばれます。

パターンD

このパターンは、パターンAと同じ状態です。
ただし、親クラスの実装において、
protocol extensionと同様の処理を行うために、
そのデフォルト実装をトップレベル func に切り出した上で、
親クラスの実装とprotocol extensionの実装の両方から、
その切り出したメソッドを呼び出すようにしています。
パターンCを書き換えてパターンAに変更したい場合に、
親クラスの実装でデフォルト実装を再利用したいができなくて不便、
という際の参考にしてください。

なぜこのような実行結果になるのか

Swiftにおいてはプロトコルのメソッドを実装したwitness tableと、
クラスのメソッドを実装したvtableというものがあります。
このようなプロトコルと継承が組み合わさったパターンでは、
インスタンスが親クラスでもサブクラスでも、
witness tableは親クラスのconformanceによって定義されたものが使われます。
そして、サブクラスのメソッドの呼び出しは、
witness tableを引いたその中で、vtableをさらに引く、
という2段ディスパッチによって行われます。

パターンAではこの2段ディスパッチによって、
サブクラスの実装が呼び出されます。

パターンBではプロトコルにメソッドの定義が無いため、
witness tableのレイアウトにそのメソッドのエントリが無いため、
protocol extensionの実装が呼び出されます。

パターンCではプロトコルにメソッドの定義があるため、
witness tableのレイアウトにそのメソッドのエントリはあるが、
親クラスにメソッドの定義が無いため、
そのエントリはprotocol extensionの実装がセットされており、
それが呼び出されます。
たとえサブクラスでメソッドを実装していても、
conformanceは親クラスで解決されており、
vtableを参照するようになっていないので、
サブクラスのメソッドは呼び出されません。


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。