iOS
mvc
設計
MVVM
RxSwift

【考察】iOS 開発において MVVM(RxSwift)は本当に MVC よりいい選択肢になるか

※本記事は個人的な考察であり、ゆえに間違った箇所などがございましたらぜひとも反論やマサカリを投げて頂けたら嬉しいです。なお、本記事における「MVVM」は、基本最近お流行りの RxSwift のデータバインディング機構に依存した、UIViewControllerViewModel を保持する MVVM を指します。

ここ数年、少なくとも日本では iOS 開発において、MVVM が非常に流行っているようです。少なくとも、いろんな勉強会やカンファレンスに参加したときに MVVM の話はよく聞きますし、筆者自身としても個人プロジェクトを除く、本業副業両方とも担当しているプロジェクトは基本設計として MVVM を採用しています。

しかし、かといって MVVM は MVC と比べる際、圧倒的な優位性を持っているのかといえば、少なくとも筆者はそう思いません。もちろん MVC と比べた MVVM の優位性はまちがいなくあります、しかしそれは「圧倒的」な優位性ではないと思います。本記事は MVVM のメリットデメリットを見ていきたいと思います。

MVVM のメリット

Android アプリとの設計の共通性

最近の Android は「Android Architecture Components(AAC)」と呼ばれる設計を標準設計として導入しています。この AAC はある種 MVVM の派生型で、Android と iOS 両方の開発を行う場合は、確かに共通の設計を持った方が、何かしらの機能追加やバグ修正のときお互いの実装が参考になることも多々あります。

特に、RxSwift を利用した場合はそもそも Rx は Swift 以外にも Java や Kotlin などの言語も対応ライブラリーがあるため、どれか一つでもマスターすれば他の言語の Rx ライブラリーを学ぶ/読むのが非常に楽です。

Cocoa MVC が発生しがちな「Massive ViewController」問題を回避しやすい

Cocoa MVC はご存知の通り、ViewController に責務を負わせ過ぎやすいので、気づいたらすぐ Fat になりがちです。この問題は Fat ViewController や Massive ViewController、一部界隈では節子とも読んだりします。それと比べて、モデルからのデータをビューで表示できる形に変換するための機能を ViewModel に切り出した MVVM は確かに Massive ViewController 問題を回避しやすいです。これも多くの人が MVC を選ばずに MVP や MVVM を選ぶ大きな理由です。

宣言型な記述で書きやすい

これは RxSwift の特徴となりますが、いわゆる Observer パターンを活用し、具体的な制御フローを考えずに、部品と部品の関係性の宣言がキモとなる設計ですので、新しく作るときは非常に書きやすいです。観察対象を subscribe してその結果に応じて適切なハンドリングを書くだけです。

ViewModel のテストが書きやすい

こちらコメント欄で @hironytic さんからいただいたメリットですが、確かに最終的な画面描画から描画に必要なロジックを ViewModel に切り出したため、Cocoa MVC と比べるとその分のテストを UITest に依存せずに UnitTest で比較的に簡単にテストが書けるというメリットがあります。

MVVM のデメリット

学習コストが高い

ここの「学習コスト」は、二つの意味を持ちます。

一つは当然、「MVVM × RxSwift」自体の学習コストです。特に今まで ReactiveX を触ったことがない人にとって、RxSwift の各種概念の習得に時間がかかるし、Swift 以外の Rx をすでに習得済みの人にとっても RxSwift(主に RxCocoa)特有の概念(Driver など)も新しく学ばなければなりません。もちろんエンジニア足るもの生涯勉強を怠るべからずですが、現実問題としてチーム内に MVVM や RxSwift が詳しくない人がいればその分の学習コストは確かに発生するので、そのコストを負担してまで MVVM を採用すべきかはやはり設計の選考に考えなくてはならない事項の一つでしょう。

そしてもう一つは、Cocoa MVC を理解した上での MVVM を使わなくてはならないという意味で、通常の MVC よりも学習負担が高いという意味があります。フロントエンドとかならこの問題はありませんが、iOS 開発において、少なくとも UIKit をベースとしたネイティブ開発において、UIKit 自身は Cocoa MVC で作られたフレームワークであるが故に、結局 Cocoa MVC に依存したライフサイクルと戦わなくてはなりません。MVC ならすぐにでも素直に実装できたコードが、MVVM なら一捻りやふたひねりをしなければ上手く動かないといったようなことも多々あります。一番簡単な例として、UIViewUIViewControllerisAppeared に相当するプロパティーがなく、UIViewControllerdidAppear()willDisappear() などのメソッドしかないので、「現在表示されているかどうか」の状態の切り替えに依存した処理はどうしても自分でそのプロパティーを追加する必要があったりします。

RxSwift への依存度が高い

RxSwift はいわゆる Observer パターンなので、当然ながら監視する側と監視される側が存在します。その監視の仕組みは RxSwift 独自の仕組みなので、当然ながら監視する側とされる側は両方 RxSwift に依存し、どっちか片方でも RxSwift に対応していなければ RxSwift に対応するためのラッパーを作らなくてはなりません。どのみち、RxSwift がある前提で動くコードとなってしまいますので、当然ながら RxSwift を導入していないプロジェクトには流用できませんし、RxSwift 前提で作られていないライブラリーの導入も一手間かかってしまうことになります。少なくとも iOS 開発において、本家は UIKit の方ですので、RxSwift が導入された前提で作られたライブラリーは非常に少ないです。そのため、気持ちよく RxSwift 環境でこれらのライブラリーを使うには多くの場合手間がかかってしまいます。

自由度が高い

メリットのように思えますが、アプリ開発、特にある程度の規模を持つアプリ開発においては「高い自由度」は必ずしもいいことではありません。

そしてこれはある意味 MVVM vs MVC よりも、RxSwift の特徴である Observer パターン vs 今までの Cocoa MVC に多く採用されている Delegation パターンの話、もしくは MVVM の流行で話題になりつつある「宣言型(Declarative)設計」vs 今まで普通によく使われてきた「命令型(Imperative)設計」の話になるじゃないかと思います。

ご存知の通り、RxSwift は宣言型記述に長けており、基本的には何かしらの Observable なプロパティーに対し、何かしらの購読(subscribe や bind、もちろん Driver としての drive も含む)をするのが特徴です。対して UIKit はもちろん KVO の手法(Objective-C のランタイムに依存しますが)をとれば同じようなこともできますが、より一般的なやり方としてはやはり UITableView などのように Delegate を作って、処理を特定な部品(多くの場合親)に移譲するのが主流です。宣言型の RxSwift は、デリゲーションのデリゲートメソッド内でしか処理を書けない Delegation パターンと違い、監視プロパティーさえ知っていれば誰でも処理を入れられるし、入れる場所も特定のメソッド内だけでなく(オブジェクトのライフサイクル内であれば)どこにでも自由に入れられます。

しかしこの自由度は、新規プロジェクトを作るときは確かに便利ですが、保守するときは逆に大変になることも多いです。例えばこんなシナリオを考えてみましょう。何かしらの要望で、とある状態の切り替えの時に、何かしらの処理を入れる必要が出てきました。その実装をプロジェクトに入って間もない、まだプログラムの全貌を把握できていない新入りが担当しました。するととりあえず新入りさんは適当にオブジェクトの生成時に、その状態切り替えを購読して実装を入れました。

一見正しそうに見えますが、しかし時間が経つとやはりその機能要らないことになりました。今度は別のプログラマーが機能改修を担当しますが、彼は以前別件でその状態の切り替えの時に別の動作をすでに実装したため、とりあえずその実装を見に行きましたがその要らない機能が見当たりませんでした。当然と言えば当然で、新入りさんは別の場所で購読しているからです。なので結局彼はこのプロパティーを購読している部品を全部洗い出す羽目になってしまいます。

上記のような仕様変更はプログラムにおいて良くあることで、仮にもしその要らなくなった機能の具体的なコードが動作からすぐに連想できればまだ探しやすいのですが、そうでない場合も多々ありますので結局保守するときは場所特定に非常に手間がかかります。それと比べて、Delegation パターンなら、その Delegate に適合している部品の Delegate メソッドだけ探せばいいので非常に探しやすく、全ての処理が一箇所にまとまっているので見通しも良好です。

ではやはり MVC を採用すべきか

これはやはりケースバイケースとしか言いようがないです。ここまで見てきた通り、MVVM は確かにメリットもたくさんありますので、少なくとも「チーム全員 MVVM(RxSwift)を熟知している」場合なら、MVVM(RxSwift)のメリットが大きいと言えます。なので MVVM は最適な設計かどうかについては、やはりチームメンバー全員で相談した上で決めるべきことで、簡単に「流行ってるから MVVM 使おうぜ」とか「なんか気持ち悪いから MVC にしようぜ」とかの軽いノリで決めるべき問題ではないでしょう。ちなみに筆者は個人開発では MVC を採用しています

宣伝

久々の記事なので軽い宣伝させてください。クラウドファンディングの共著案件ですが、おかげさまで本を執筆することになりました。本記事とも関係がある「iOS アプリ設計パターン入門」という本です。私は MVC についての第 5 章を執筆しますが、私以外にも @takasek@d_date@ktanaka117@marty-suzuki および @susieyy(敬称略)と言った豪華執筆陣が担当しております!設計の導入で悩んでいるあなたにはきっとお役に立てると思いますので、もし興味ありましたらぜひご応援ください :tada:
https://peaks.cc/iOS_architecture
スクリーンショット 2018-06-11 7.19.21.png