はじめに
以前書いた今更MVCとかでiOSアプリつくってみた(Swift)・改の続きです。なんか面談のときにちょこちょこアーキテクチャについて聞かれたので今度は MVP について考えてみます。
前記事でも書きましたが、アーキテクチャについてネットで色々検索すると言ってることがそれぞれ微妙に違うのがいっぱい出てきます。アーキテクチャについて学びたいと思うなら iOSアプリ設計パターン入門 を読みましょう!
この記事は iOSアプリ設計パターン入門 を参考にしていますが、あくまで私個人の意見です。
わりと長くなったのでめんどくさい人はソースだけでもどうぞ
長いの読みたくない人ように結論だけ
たぶん MVP で一番重要なのはプレゼンテーションロジックのテストがしやすいこと!!
MVP の種類
MVPには大きく分けて下記の3つがある
- MVP (Taligent)
- MVP (Supervising Controller)
- MVP (Passive View)
MVP (Taligent)
1996年に原初 MVC を他プラットフォームでも使えるようにするため再定義したもの。(TaligentはAppleとIBMが合同出資した会社の名前)
Controller (入力系 以下 C) を一般化した存在として Presenter (以下 P) を置いた。
責務を細分化し過ぎたせいかあまり実用化されていない... (他の MVP に影響は与えている)
* 第 4 章 アーキテクチャのパターンを鳥瞰するの 4.2.4 Model-View-Presenter (MVP) P.56
MVP (Supervising Controller)
2000年に Dolphin Smalltalk 開発環境で採用するため考案された。
Application Model パターンの MVC と MVP (Taligent) の影響を受けている。
(Application Model パターンの MVC は M と V, C の間にプレゼンテーションロジックを担う Presentation Model を置いたやつ)
- View (以下 V)
C (入力系) と V (出力系) を統合したやつ (単純なプレゼンテーションロジックを持つ) - Presenter (以下 P)
プレゼンテーションロジックを担当する - Model (以下 M)
UI 関係以外すべてを担当する
V の表示反映方法はオブザーバー同期とフロー同期2つの方法を用いる
* 詳細は 第 4 章 アーキテクチャのパターンを鳥瞰するの 4.2.4 Model-View-Presenter (MVP) P.56 ~ P.59と第 6 章 MVP
MVP (Passive View)
2005年にテストしやすい TDD サイクルを回せるアーキテクチャとして誕生した。
それぞれの役割はだいたい Supervising Controller と同じ (プレゼンテーションロジックは完全に P が担当する)
V の表示反映方法はフロー同期のみ
Passive View というのはオブザーバー同期をやめた V ということ。 (M を監視して能動的に更新するのではなく受動的に更新される V )
* 詳細は第 4 章 アーキテクチャのパターンを鳥瞰するの 4.2.4 Model-View-Presenter (MVP) P.60 ~ P.61と第 6 章 MVP
iOS における MVP
MVP (Taligent) はあまり使われていないみたいなので下記2つに絞る。
- MVP (Supervising Controller)
- MVP (Passive View)
MVP (Supervising Controller)
iOS における構成はこんな感じ
MVP (Passive View)
iOS における構成はこんな感じ
Supervising Controller の場合 V が担う「簡単な P ロジック」というものに個人差が生まれる危険性があるので今回は Passive View についてのみ言及します。(いまいち Supervising Controller わかってない...)
以降、MVP は Passive View のことを指します。
なぜ MVP を導入するのか?
iOS で MVP を導入する理由は下記2点だと思います。
- プレゼンテーションロジックのテストするため
- FatViewController を防止するため
上記について Cocoa MVC の場合 (以降、MVC は Cocoa MVC のことを指します。)
- プレゼンテーションロジックのテストするため
C にプレゼンテーションロジックがあるのでテストは困難。 - FatViewController を防止するため
C が M と V の仲介をし、プレゼンテーションロジックも担っているので太りやすい。
これらを解決するために MVP を導入すると考えています。(個人の意見です。)
お天気アプリ実装
MVP の考え方をわかった気になったところでいよいよ実装にはいっていきます。
Cocoa MVC でつくったやつを MVP に分けていきます。
こういうやつ
一覧 | 地方フィルター | お天気 |
---|---|---|
MVP では P と V が互いに参照を持つがどちらを主にするかは定まっていないらしい。iOS では ViewController(V)が必須なので V に P を strong で持たせ P は V を weak で持つのが一般的らしい。
M, V を別モジュールにしていたが V <-> P が相互に参照するので V のモジュールはメインと統合する。
MVP による分割
View
View がやるのは見た目に関すること、画面遷移系(これは VC が V である以上こいつがやるしかない)、ユーザーアクションの受け取りなど。
MVC の V と C の役割の プレゼンテーションロジックと M との仲介以外はこいつがやる。
- View の設定 (add したりとか)
- View のレイアウト設定
- ボタンとかのアクション実装 (P に伝達)
- 画面遷移
Model
Model がやるのは UI 関係の処理以外すべて。MVC の M と同様。
- 天気の取得(通信まわり)
- お気に入りの保存
- 都道府県の情報取得
- どんなデータを受け取るかの表現([String: Any] で受けているところを Weather などの型で表現する)
- 都道府県のフィルター処理(これはもしかしたら P ロジックで P がやるべきかもしれない)
- データが更新されたら P に通知する
Presenter
Presenter がやるのはプレゼンテーションロジックと M と V の仲介。MVC の C がやってた一部をこいつがやる。
- 表示用のデータ加工(ex. 最高気温とかを加工して 30℃ とかにする)
- プレゼンテーション関連の状態保持(ex. ボタンのチェック状態の保持とか)
- V の表示更新
- M の更新を受け取る (コールバックとかデリゲート??)
とりあえずは上のように分離する。ここでもう一度 MVP 導入の目的について考える。
- プレゼンテーションロジックのテストするため
- FatViewController を防止するため
2. は P に分離したのである程度達成されたが、1.はまだ怪しい。P のテストをしようと思うと M と V も必要になってくる。テストをしやすくするためにこの部分を Protocol として切り出していく。
Protocol 切り出し
都道府県一覧画面の部分をこんな感じで切り出してみた。
PrefectureListPresenter プロトコル
protocol PrefectureListModelInput {
var prefectureList: [CityData] { get }
func updateFavoriteIds(cityId: String) -> Result<[String], FavoritePrefectureDataStoreError>
var favoriteIds: [String] { get }
func prefectureFiltered(isFilterFavorite: Bool,
favoriteIds: [String],
selectedAreaIds: Set<Int>) -> [CityData]
}
protocol PrefectureListPresenterOutput: AnyObject {
func showProgress()
func hideProgress()
func showAlert(message: String)
func showWeatherViewController(model: WeatherModelInput)
func showAreaFilterViewController(button: UIButton)
func updateViews(with data: PrefectureListViewData)
}
extension PrefectureListModel: PrefectureListModelInput {
}
import Models
は P でのみやるべきかと思い、 extension PrefectureListModel: PrefectureListModelInput
のようにした。
これで P のテストがしやすくなった。
上記のように分離していき下記が完成した。
テストはこんな感じ
テストを書くのが目的みたいに書いてたので無理やり色々テストを書いてみましたがこれであってるのかは不明(テストが一番迷走した)
Cocoa MVC と比べて V の再利用性は低下してしまったように思う。(M は独立したままなので再利用性は高い)。まあでも、下記の導入の目的は達成されたと思う。
- プレゼンテーションロジックのテストするため
- FatViewController を防止するため
さいごに
以上が現時点での私の MVP の認識です。今後やっぱちがうなとか思ったら都度修正していきます
記事を書いたのは他の人の意見も聞きたいなと思ったからなので「その認識で合ってるよ」とか「全然ちゃうわ。頭わいてんのか?」など優しく指摘してくださると幸いです
とりあえずみんな iOSアプリ設計パターン入門 を読もう