この記事は 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があり、どれも見た目や機能はだいたい同じです。
ですが、新着順・人気順には運営からのお知らせを出したい(検索結果には出したくない)など、各画面で微妙に要件が異なります。(実際のアプリ開発でもよくあると思います)
これらをすべてBaseViewControllerみたいな親クラスを使って吸収すると、次のような問題が発生します。
- BaseViewControllerの肥大化
- 各画面の違いを吸収するため、BaseViewControllerにすべて突っ込むことになる
- 影響範囲が無駄にでかくなる
- BaseViewControllerを修正すると、それらを継承しているすべてのViewControllerが影響範囲になる。お知らせに関わるコードのみ修正しても、意図せず検索画面に影響でる可能性がある(複数人開発だと特にこわい)。
- 仕様変更に弱い
- 人気順だけお知らせの文言を変えたい場合、BaseViewControllerの中で分岐する必要があり、コードの見通しが悪くなる
- 記事のリストとは全く異なる画面(自分のプロフィール画面とか)にもお知らせを出したくなった場合、コードをコピペすることになる。将来的にお知らせのロジックを変更する場合、修正箇所が増える。
参考:iOSアプリの設計でBaseViewControllerのようなのは作りたくない - Qiita
protocol extensionでmixin的なものを実現する
継承ではなく、mixinを使うことで上記の問題は解決します。
TableViewControllerに対して、headerViewにお知らせを表示する機能を追加してみましょう。
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
}
}
class NewTopicsTableViewController: UITableViewController, Noticeable {
override func viewDidLoad() {
super.viewDidLoad()
self.getNotice()
}
}
細かい部分は省略しましたがだいたいこのような感じです。
ポイントは where Self: UITableViewController
です。これでprotocol extensionの対象をUITableViewControllerに絞ることができます。つまりはUITableViewControllerに対してmixinできます。self.tableView
とか書いても普通に補完してくれます。
注意事項
便利なprotocol extensionですが、格納型プロパティを持つことができません。そのため実現したい機能によってはうまく実装できないかもです。1
また、格納型プロパティをもてないので、厳密にはmixinではなく、traitと呼ぶようです。mixinの方が馴染みがあってわかりやすいと思ったので今回はmixin的なものと書きました。
参考:Mixins and Traits in Swift 2.0
おわりに
明日はスクーにも出たことがある @migi がJS初学者に向けた話をするみたいです。お楽しみに!
-
一応無理やり格納型プロパティをもたせる方法はあります。Swiftのextensionでstored propertyを追加する?(黒魔術は閉じ込める) - TOKOROM BLOG ↩