Swiftのprotocol extensionでmixin的なものを実現する

![image](https://qiita-image-store.s3.amazonaws.com/0/12352/d4237ddd-ed38-5bb9-f85b-100276e7b26f.png)

この記事は [Goodpatch Advent Calendar 2015](http://qiita.com/advent-calendar/2015/goodpatch) 3日目の記事です。昨日は @daichi_ito の[今日からはじめる命名戦略](http://t.snkz.org/source-code-naming/)でした。

GoodpatchでもSwiftを使うことが増えてきました。僕自身、今年5月に入社して以来ずっとSwiftを書いてます。今日はSwift2で新しく追加されたprotocol extensionを使って、mixin的なものを実現する方法を紹介します。これを上手く使うと、コードの再利用が柔軟にできて便利です！

# protocol extensionとは？
protocol（インタフェースの定義）を拡張し、メソッドの実装を追加できる機能です。

```swift
protocol MyProtocol {
    func hoge()
}

extension MyProtocol {
    func hoge() {
        print("hoge")
    }
}

class MyClass: MyProtocol {
}

let myInstance = MyClass()
myInstance.hoge() // hoge と出力される
```

# mixinとは？
あるクラスに対して、外側からメソッドを追加します。共通の機能を１つのコードにまとめて、複数のクラスで再利用できます。


## 継承じゃだめ？
継承でもコードの再利用・共通化はできますが、柔軟性に欠けます。

例えば、何かしら記事のリストを表示するアプリがあったとします。新着順、人気順、検索結果を表示する３つのViewControllerがあり、どれも見た目や機能はだいたい同じです。

ですが、新着順・人気順には運営からのお知らせを出したい（検索結果には出したくない）など、各画面で微妙に要件が異なります。（実際のアプリ開発でもよくあると思います）


![image](https://qiita-image-store.s3.amazonaws.com/0/12352/3afb6af4-2012-fdf6-f71d-c58e2478505f.png)



これらをすべてBaseViewControllerみたいな親クラスを使って吸収すると、次のような問題が発生します。

- BaseViewControllerの肥大化
    - 各画面の違いを吸収するため、BaseViewControllerにすべて突っ込むことになる
- 影響範囲が無駄にでかくなる
    - BaseViewControllerを修正すると、それらを継承しているすべてのViewControllerが影響範囲になる。お知らせに関わるコードのみ修正しても、意図せず検索画面に影響でる可能性がある（複数人開発だと特にこわい）。
- 仕様変更に弱い
    - 人気順だけお知らせの文言を変えたい場合、BaseViewControllerの中で分岐する必要があり、コードの見通しが悪くなる
    - 記事のリストとは全く異なる画面（自分のプロフィール画面とか）にもお知らせを出したくなった場合、コードをコピペすることになる。将来的にお知らせのロジックを変更する場合、修正箇所が増える。


参考：[iOSアプリの設計でBaseViewControllerのようなのは作りたくない - Qiita](http://qiita.com/yimajo/items/8ef1850e1362bd3bf3d8)

# protocol extensionでmixin的なものを実現する
継承ではなく、mixinを使うことで上記の問題は解決します。

TableViewControllerに対して、headerViewにお知らせを表示する機能を追加してみましょう。

```swift:Noticeable.swift
protocol Noticeable {
    func getNotice()
    func openNotice(url: NSURL)
    func closeNotice()
}
extension Noticeable where Self: UITableViewController {
    func getNotice() {
        NoticeAPIClient.find()
        .success { (notice: Notice) -> Void in
            let noticeView = NoticeView.view()
            noticeView.notice = notice
            self.tableView.tableHeaderView = noticeView
        }
    }

    func openNotice(url: NSURL) {
        let webVC = //WebViewでお知らせを表示
        self.navigationController?.presentViewController(webVC, animated: true, completion: nil)
    }

    func closeNotice() {
        guard let noticeView = self.tableView.tableHeaderView as? NoticeView else { return }

        self.tableView.tableHeaderView = nil
    }
}
```

```swift:NewTopicsTableViewController.swift
class NewTopicsTableViewController: UITableViewController, Noticeable {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.getNotice()
    }
}
```

細かい部分は省略しましたがだいたいこのような感じです。

ポイントは **`where Self: UITableViewController`** です。これでprotocol extensionの対象をUITableViewControllerに絞ることができます。つまりはUITableViewControllerに対してmixinできます。`self.tableView`とか書いても普通に補完してくれます。

参考：[プロトコル拡張の話？ #WWDC21cafe](http://www.slideshare.net/tomohirokumagai54/wwdc21cafe)


## 注意事項
便利なprotocol extensionですが、格納型プロパティを持つことができません。そのため実現したい機能によってはうまく実装できないかもです。[^1]

また、格納型プロパティをもてないので、厳密にはmixinではなく、traitと呼ぶようです。mixinの方が馴染みがあってわかりやすいと思ったので今回はmixin的なものと書きました。

参考：[Mixins and Traits in Swift 2.0](http://matthijshollemans.com/2015/07/22/mixins-and-traits-in-swift-2/)

# おわりに
明日はスクーにも出たことがある @migi がJS初学者に向けた話をするみたいです。お楽しみに！

[^1]: 一応無理やり格納型プロパティをもたせる方法はあります。[Swiftのextensionでstored propertyを追加する？（黒魔術は閉じ込める） - TOKOROM BLOG](http://www.tokoro.me/2015/11/08/has-associated-objects/)