LoginSignup
163

More than 5 years have passed since last update.

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

Posted at

image

この記事は Goodpatch Advent Calendar 2015 3日目の記事です。昨日は @daichi_ito今日からはじめる命名戦略でした。

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

protocol extensionとは?

protocol(インタフェースの定義)を拡張し、メソッドの実装を追加できる機能です。

protocol MyProtocol {
    func hoge()
}

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

class MyClass: MyProtocol {
}

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

mixinとは?

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

継承じゃだめ?

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

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

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

image

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

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

参考:iOSアプリの設計でBaseViewControllerのようなのは作りたくない - Qiita

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

継承ではなく、mixinを使うことで上記の問題は解決します。

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

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
    }
}
NewTopicsTableViewController.swift
class NewTopicsTableViewController: UITableViewController, Noticeable {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.getNotice()
    }
}

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

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

参考:プロトコル拡張の話? #WWDC21cafe

注意事項

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

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

参考:Mixins and Traits in Swift 2.0

おわりに

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


  1. 一応無理やり格納型プロパティをもたせる方法はあります。Swiftのextensionでstored propertyを追加する?(黒魔術は閉じ込める) - TOKOROM BLOG 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
163