SwiftのProtocolExtensionでBaseViewControllerの肥大化を解決！そして、必ず抑えておきたいデメリット


## BaseViewControllerの肥大化

ある特定の画面への遷移など、共通の処理を書きたいことはしばしばあります。

下の例は`MyViewControllerA`及び`MyViewControllerB`のどちらからも`MyViewControllerC`へ遷移したい時のコードです。

``` swift
class MyViewControllerA: UIViewController {
    // ...省略...
    func showViewControllerC() {
        navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
    // ...省略...
}

class MyViewControllerB: UIViewController {
    // ...省略...
    func showViewControllerC() {
        navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
    // ...省略...
}

class MyViewControllerC: UIViewController {
    // ...省略...
}
```

`MyViewControllerA`及び`MyViewControllerB`のどちらにも実装されている`showViewControllerC`メソッドを共通化したい時、一つの選択肢として**BaseViewController**（共通のスーパークラス）を作る方法があります。

``` swift
class MyBaseViewController: UIViewController {
    // ...省略...
    func showViewControllerC() {
        navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
    // ...省略...
}

class MyViewControllerA: MyBaseViewController {
    // ...省略...
}

class MyViewControllerB: MyBaseViewController {
    // ...省略...
}

class MyViewControllerC: UIViewController {
    // ...省略...
}
```

これでこのコードはすっきりして悪くはないのですが、共通の処理を`MyBaseViewController`に記述しすぎて肥大化するということがしばしば起こります。

また、意外とはまりがちなのが、`UITableViewController`や`UICollectionViewController`のサブクラスにも同じ処理を実装したくなった場合です。
BaseViewController方式には、こういったところでいつか限界点が訪れることになるでしょう。

もう一つの方法としては、

``` swift
extension UIViewController {
    // ...ここをどんどん拡張していく...
    func showViewControllerC() {
        navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
}
```

のような実装方法も考えられます。が、アプリ固有のコードを`extension`でどんどん拡張していくのもあまり良い経験がありません。

## ProtocolExtensionを使う

上記の問題はProtocolExtensionでMixinを実装することで解決できます。

一般的にMixinというと、インターフェースを定義してそれぞれのクラスが実装を担保するという形が多いと思いますが、ProtocolExtensionで行えるMixinはもっともっと強力です。

プロトコルに実装を記述することができるので、あるプロトコルを採用するだけで実装を組み込むことができてしまうのです。

下記のコードを見てみましょう。

``` swift
protocol TransisionProtocol {
    func showViewControllerC()
}

extension TransisionProtocol where Self: UIViewController {
    func showViewControllerC() {
        navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
}

class MyViewControllerA: UIViewController {
    // ...省略...
}

class MyViewControllerB: UIViewController {
    // ...省略...
}

class MyViewControllerC: UIViewController {
    // ...省略...
}

// Mixin
extension MyViewControllerA: TransisionProtocol {}
extension MyViewControllerB: TransisionProtocol {}
```

### 解説

`TransisionProtocol`インターフェースを宣言します。

``` swift
protocol TransisionProtocol {
    func showViewControllerC()
}
```

`TransisionProtocol`に実装を定義します。

``` swift
extension TransisionProtocol where Self: UIViewController {
    func showViewControllerC() {
        self.navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
}
```

ここで重要なのが、`where Self: UIViewController`の部分です。
SelfがUIViewControllerを継承していることが保証されるので、実装側で、selfがあたかもUIViewControllerであるとして記述することができます。

あとは、各ViewControllerは`TransisionProtocol`を採用するだけです。

``` swift
// Mixin
extension MyViewControllerA: TransisionProtocol {}
extension MyViewControllerB: TransisionProtocol {}
```

これで各ViewControllerに`showViewControllerC`の実装を組み込むことができます。

### コツ

目的ごとにプロトコルを宣言することで特定のクラスやプロトコルが肥大化するのを抑えることができるでしょう。
そうすれば、どのViewControllerがどのProtocolを採用しているかを見ればやりたいことが一目瞭然になります。

## デメリット

ここまで読めばメリットはなんとなくわかると思います。
ただ、この方法にはメリットだけではなく、必ず抑えて置かなければいけないデメリットも存在します。
変なところではまらないために、このデメリットの方を強調したいです。

* クラス側でプロトコルの実装を上書きしても気づけない
* 名前が被らないように気を配る必要がある

などなどありますが、前者が一番大きなデメリットだと思います。
なぜかというと、コンパイルエラーにならないからです！

下記の例を見てみましょう。

``` swift
protocol TransisionProtocol {
    func showViewControllerC()
    func pop() // 追加
}

extension TransisionProtocol where Self: UIViewController {
    func showViewControllerC() {
        navigationController?.pushViewController(MyViewControllerC(), animated: true)
    }
    
    // 戻る画面遷移
    func pop() {
        navigationController?.popViewControllerAnimated(true)
        print("TransisionProtocol pop")
    }
}

class MyViewControllerA: UIViewController {
    // ...省略...
    
    // 実装を上書き
    func pop() {
        // なにかpop的な別の処理
        print("MyViewControllerA pop")
    }
}

class MyViewControllerB: UIViewController {
    // ...省略...
}

class MyViewControllerC: UIViewController {
    // ...省略...
}

// Mixin
extension MyViewControllerA: TransisionProtocol {}
extension MyViewControllerB: TransisionProtocol {}
```

`TransisionProtocol`に`pop`という、画面を戻すためのメソッドを追加してみました。

この状態でもし仮に、`MyViewControllerA`に`pop`というpop的動作の何かを実行するメソッドを追加したとします。
継承の時のように`override`をつけなくても`pop`の実装を上書きしてしまうことができるため、この状態で`pop`を実行すると上書きされた方が実行されることになります。

``` swift
myViewControllerA.pop()  // 出力: MyViewControllerA pop
myViewControllerB.pop()  // 出力: TransisionProtocol pop
```

このように無意識のうちに実装を上書きしてしまい、予期せぬ動作になることはバグの原因です。

プロトコル側のメソッドにあまりにもシンプルな名前を付け過ぎるとこのようなことが起きやすくなるでしょう。

## 最後に

この方法を実際のプロダクトに採用し始めてから日が浅いので、もっと別のデメリットに遭遇することがあるかもしれません。
また、他にもデメリットがあるという方がいらっしゃいましたらご意見いただけるとありがたいです！