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

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

# 書いたコード(簡略化)
```swift:
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()`を呼び出します。
期待する結果としては、どちらも`SomeClass`の`hoge()`が呼ばれてほしいです。
実際の結果は以下の通り。

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

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

# 何が起きているのか
protocol extensionの挙動については素晴らしい記事があるので、ぜひこちらを読んで下さい。
[Swiftのプロトコルエクステンションの罠](https://qiita.com/omochimetaru/items/17cdbb5a77972c82a781)

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

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

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

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

```swift:
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
```
これで期待通りの結果になりました！

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

```swift:
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: