post Image
【Swift】protocol extensionでデフォルト引数を使うときはメソッドの宣言に気をつけよう

それはテストを書いている時に起きた・・・:fearful:
プロダクトコードではprotocol extensionを使っている部分をモックで置き換えようとしたが、何故かモックのメソッドが呼ばれない!:scream:
確かにモック内でプロトコルのメソッドを定義しているはずなのに・・・:confounded:

書いたコード(簡略化)

protocol SomeProtocol {
    func hoge(string: String)
}

// プロダクトコードのときはこちらを通ってほしい
extension SomeProtocol {
    func hoge(string: String = "") {
        print("\(string)SomeProtocol")
    }
}

// テスト用のモックだと思ってください
// テスト時はこちらを通したい
class SomeClass: SomeProtocol {
    func hoge(string: String = "") {
        print("\(string)SomeClass")
    }
}

// 型としてはプロトコル
let some: SomeProtocol = SomeClass()
some.hoge(string: "hello ") // これらの
some.hoge()                 // 出力は・・・?

あるプロトコルにhoge(string:)というメソッドが定義してあって、protocol extensionとSomeClassと、どちらにも実装があるという状態です。
その時に、型としてはSomeProtocolで実体はSomeClassのプロパティからhoge()を呼び出します。
期待する結果としては、どちらもSomeClasshoge()が呼ばれてほしいです。
実際の結果は以下の通り。

some.hoge(string: "hello ") // => hello SomeClass
some.hoge()                 // => SomeProtocol

some.hoge(string: "hello ")については期待通りSomeClassのメソッドが呼ばれましたが、some.hoge()はprotocol extensionのメソッドが呼ばれてしまいました。

何が起きているのか

protocol extensionの挙動については素晴らしい記事があるので、ぜひこちらを読んで下さい。
Swiftのプロトコルエクステンションの罠

今回のコードに関連する部分だけざっくりまとめると、

  • プロトコル本体に宣言があるメソッドを呼び出す場合には動的ディスパッチ
  • プロトコル本体に宣言が無い場合は静的ディスパッチ

になるということです。
もうおわかりかと思いますが、プロトコル本体で宣言があるのは、hoge(string:)というラベルを持ったメソッドのみで、ラベルなしのhoge()は宣言がありません。
そのためsome.hoge(string: "hello ")は実体であるSomeClassのメソッドが呼ばれ、some.hoge()は型であるSomeProtocolのメソッドが呼ばれました。

修正

プロトコル本体にhoge()の宣言が無いことが問題だったので、追加してやれば解決です。
デフォルト引数はある意味がなくなってしまったので消しています。

protocol SomeProtocol {
    func hoge() // 追加
    func hoge(string: String)
}

extension SomeProtocol {
    func hoge() { // 追加
        self.hoge(string: "")
    }

    func hoge(string: String) {
        print("\(string)SomeProtocol")
    }
}

class SomeClass: SomeProtocol {
    func hoge() { // 追加
        self.hoge(string: "")
    }

    func hoge(string: String) {
        print("\(string)SomeClass")
    }
}

let some: SomeProtocol = SomeClass()
some.hoge(string: "hello ") // => hello SomeClass
some.hoge()                 // => SomeClass

これで期待通りの結果になりました!

おまけ

実は上のコードはもっとサボることができます。

protocol SomeProtocol {
    //func hoge()
    func hoge(string: String)
}

extension SomeProtocol {
    func hoge() {
        self.hoge(string: "")
    }

    func hoge(string: String) {
        print("\(string)SomeProtocol")
    }
}

class SomeClass: SomeProtocol {
//    func hoge() {
//        self.hoge(string: "")
//    }

    func hoge(string: String) {
        print("\(string)SomeClass")
    }
}

let some: SomeProtocol = SomeClass()
some.hoge(string: "hello ") // => hello SomeClass
some.hoge()                 // => SomeClass

コメントアウトした部分が上のコードとの差分です。
この場合のsome.hoge()は一度SomeProtocol.hoge()を経由してSomeClass.hoge(string:)を呼び出します。
今回はhoge()が中身の無いメソッドなのでこれでもできますが、普通はちゃんとプロトコルで宣言して実装するのが良いかと思います。

おわりに

protocol extensionはコードを共通化するのに非常に便利な機能なのですが、途中リンクを載せた記事の通り、若干不思議な挙動をするので、注意が必要です:warning:
デフォルト引数も便利なのですが、メソッドのラベルを書かないことでラベルありのメソッドと別ものと判断されてしまうのは罠でした:bulb:
これで詰まった人とかいないんですかね:question:
ちょっとニッチかもしれないけど、誰かしらの参考になれば幸いです:ok_woman:


『 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

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