※2022/04/23 追記
本記事の続編として、以下の記事を書きましたので、合わせて御覧ください。
仕事でSwiftUIでTCAを使ってみて、かなり知見がたまったので、その解説です。
MVVMからTCAへの移行を考えているのであれば、参考になると思います。
宣言的UIに、MVVMって不要なのでは?
iOS開発の現場で、宣言的UIが当たり前に使われるような時代になりました。
SwiftUIの開発体験、素晴らしい です。最高です。
しかし最近、SwiftUIで当たり前のように 「MVVMで開発しよう」 となったときに、
「ほんとにそれでいいんだっけ?」 と疑問に思いました。
自分の考えを深掘ってみると
-
問い:
- iOS開発で、宣言的UIにMVVMを採用することは本当にいいんでしたっけ?
-
結論:
- 「SwiftUIを使うのであれば、MVVMを採用するのは止めよう」
-
理由:
- ViewModelの存在は、宣言的UIの時代には必要ないと考えたから。
という結論に達しましたので、考えをこの記事にまとめます。
ViewModelって不要じゃないですか?
iOS開発の現場で、深く考えずにアーキテクチャにMVVMを選択していませんか?
もしあなたがSwiftUIにおけるMVVMパターンが厄介だと感じたり、実装時にそれほど有用ではないと感じたら、それは MVVMのデザインパターンがSwiftUIの特徴に合っていない のではないでしょうか。
これまでのiOSアプリの開発では、MVVMアーキテクチャが採用されることが多かったです。
しかし、宣言的UIであるSwiftUIの登場により、SwiftUI時代に合ったアーキテクチャを検討する必要が出てきた のです。
以前、ぼくは、以下のツイートをしました。
MVVM不要論は、海外でも度々議論になる
AppleのデベロッパーフォーラムやReddit, Youtubeでも、「iOS開発(SwiftUI)でMVVMを採用するのは止めよう」 という投稿を目にすることもあります。
Stop using MVVM for SwiftUI
Is MVVM an anti-pattern in SwiftUI?
STOP Using MVVM with SwiftUI
STOP using MVVM for SwiftUI | Clean iOS Architecture
I was wrong! MVVM is NOT a good choice for building SwiftUI applications
Stop using MVVM with SwiftUI(本記事の英語版)
SwiftUIでViewModelを使うのは、
「ホバーボードで空を飛べるのに、わざわざ車輪をくっつけて地べたを走ってるようなものだ」
という皮肉の効いた投稿もなされています。
なぜ、われわれは、MVVMを選択してしまうのか
最近、iOS開発でSwiftUIを使う機会が増えました。
SwiftUIを使ったアプリで、アーキテクチャをどうするか問題に頭を悩ませています。
モバイルアプリ開発をしていると、なぜかアーキテクチャは、MVVMを選択しがち です。
ぼくは 「なぜっ?」 って疑問に思います。
これは、モバイルアプリ開発の古くからの伝統で、
GoogleがAndroid開発において、MVVMを提唱することから始まっています。
(※1:あくまで提唱しただけ※2:大元の発祥はMicrosoft WPF)
元々はAndroid開発の現場からMVVMの採用が始まったのですが、それをiOSの開発にも持ち込んで当たり前のように広まってしまったのが現状です。
しかしながら、
「Google が推奨するアーキテクチャは MVVM だ」
と言った事実は存在しません。(自分の探した限りでは)
現在、Google公式のアーキテクチャガイドから、「MVVM」という言葉は消えています。
※「ViewModel」という言葉は微妙に残っていていますが、ぼくは正確な意味が汲み取れていません。
しかもMVVMの発祥はAndroid開発であって、(※追記:間違いです。発祥はMicrosoftです。)
そもそも、本家Appleは、MVVMという言葉を使ったことすらありません。
Googleも 「最終的に、Jetpack Composeでは、MVVM/ViewModelを無くしたい」 という発言が見え隠れするので、Android開発でもMVVMというワードが消えていくのかなと雰囲気を感じ取っています。(※個人の感想です)
以下は、 GoogleのJetpack Compose開発者 の方のツイートです。
「ViewModelは、Jetpack Composeに必要ない。 削除することを推奨する」と明言しています。
AAC ViewModel は Android 用の雑なもので、悪いパターンを増殖させる理由はないと思います。データレイヤーが適切に設計されていれば、AAC ViewModel を使う必要がないことに気づくはずです。
また、Jetpack ComposeでViewModelを使わずに、Composable関数を使って状態とロジックを切り出す方法について記事を書きましたので、合わせてご覧ください。
MVVMとは
そもそも、iOS開発のMVVMとは、まだSwiftUIが無い時代に、
RxSwift等のリアクティブライブラリを使って、ViewとViewModelの値をバインディング していた時代に、用いられていたものです。
View <-> ViewModel <-> Model
Binding
class ViewController {
override func viewDidLoad() {
viewModel.username
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
SwiftUI.ViewにViewModel相当の機能が含まれている。
しかし、SwiftUIのViewには、元からデータバインディングの機能が含まれています。
言い換えると、 「UIKit.View + MVVM」でやっていたことが、「SwiftUI.View単体」で出来る、と捉えることができます。
つまり、SwiftUI.Viewというのは、すでに「View + ViewModel」の機能を持っていて、
直接Modelの値をリアクティブにViewに反映することが可能なのです。
SiwftUI.View <-> Model
Binding
宣言的UIの登場で、ViewModelは、存在理由を失っている
つまり、 SwiftUIにViewModelのデータバインディング機能が内包された時点で、 ViewModelは存在理由を失い、「ViewModel」という言葉自体も、なんだか意味がよくわからない言葉 になってしまいました。
ViewModelという言葉は 非常に曖昧 で人によって定義が違い、そもそもViewModelレイヤーでやるべきことが何なのか明確化されてないので、とりあえず面倒事は全部押し付けられがちです。
本来、別のレイヤーですべきこともViewModelにつっこんでたりします。
MVVMを採用すると余計な複雑性を生んでしまう。
SwiftUIでMVVMを採用してしまうと「ViewModel」という余計なレイヤーを挟んでしまうため、冗長で複雑になります。
データフローについて、ViewとModelがViewModelという中間レイヤーを挟んだ複雑なデータフローになってしまいます。
Apple公式でも示されている、単方向データフローとは乖離することを、懸念しています。
MVVMにおけるViewModelは単方向データフローを実現するものではありません。
(※単方向データフローを実現するために「MVVMではないViewModel(ObservableObject)」を使うことに関しては、別の話であって、これを混同しやすいことも問題なのではないかなと思います。)
宣言的UI時代ではデータフローを単方向にするほうがメリットがあると思います。
SwiftUI開発では、今まで以上にデータフローや状態管理が大切になります。
それには、Single Source of Truthにするための状態管理する場所や、Property Wrapperの活用して、コンポーネント間のデータフローを考える必要があります。
デザインパターンを書くために、デザインパターンを書いてはいけない
SwiftUI.View <-> ViewModel <-> Model
Binding
MVVM on MVVM
「SwiftUIでMVVMを採用してしまうこと」を言い換えると「SwiftUI + MVVM = MVVM on MVVM」となり、 同じことを二回言う進次郎構文 になってしまいます。
「MVVMのおかげで、MVVMできてよかった」状態になっています。
それって、「デザインパターンを書くために、デザインパターンを書くこと」 になって、目的を見失っていませんか?
デザインパターンの有用性は、要件や環境によって、薬にも毒にもなりえます。
特定の場面でメリットがある一方で、欠点もあります。
過剰なアーキテクチャ指向は、自尊心を満たしてくれますが、身の丈に合ったアーキテクチャで開発運用するのが最も合理的な選択だと思います。
アメリカのことわざに 「ハンマーしか持っていなければ、すべてが釘のように見える」 というものがあります。
ひとつの手段に囚われると、 その手段が目的化してしまう ことへの戒めとして語られることが多い言葉です。
あなたがハンマーで叩いているものは、釘ではなくネジの可能性 はありませんか?
ネジ回しを使ったほうが、スムーズに作業が捗る場合も多いのではないでしょうか。
MVVMだけにとらわれると、MVVMすることが目的化しやすいのです。
「MVVM on MVVM」にならないためにも、ViewModelのレイヤーを無くしたほうがシンプル だと考えます。
※追記:コメント欄で以下のご指摘をいただきました。
「Modelが出力したデータがViewにとって不適切な場合にその中間に立って値を変換する」という責務がMVVMでのViewModelには任されていると思います。
宣言的UIが到来した今でもその点で存在理由は失っていないと思いますがどうでしょうか?
たしかに、おっしゃる通りだと思います。
ViewとModelを直接依存させたくなくて、中間レイヤーを設けたいという気持ちもわかります。
ですので、「Modelが出力したデータがViewにとって不適切な場合にその中間に立って値を変換する」ためにViewModelレイヤーを設けるという考えを否定するつもりはありません。
よって、「ViewとModelの直接依存は許容できて、これらの間の変換を行う中間レイヤーが不要な場合にViewModelレイヤーを省略しても良い」
というもう一つの理由もあるということを、ここに追記させていただきます。
ご理解をいただけると幸いです。
本記事の説明不足により、誤解を招いてしまい、申し訳ありません。
※ただ、その中間レイヤーに「ViewModel」という名前が適切かどうかは、よく考えたほうが良いと思います。
React/Vue/Flutter での開発では、MVVMは採用されていない。
同じく宣言的UIを使っている「React/Vue/Flutter」の分野では、MVVMというアーキテクチャは採用されておらず(宣言的UIに内包されている)、SwiftUIにおいてだけMVVMを採用するのはおかしいなと感じます。
※Flutterで「MVVM」を使ってる場合 もありますが、あれはMVVMではない と考えます。Providerというアーキテクチャの枠組みの中で、 "Providerで運んでいるStateNotifierの名前" を 「ViewModelという名前にしたもの」 という認識です。アーキテクチャとしては「Provider」なので自分の中では「MVVM」としてはカウントしないこととします。Android開発での「MVVM」を引きずってFlutterでも「ViewModel」を作るのは個人的には良くないことなのかなと感じています。
「ViewModel」という言葉を使うのはモバイル界隈の方が多いので、他の分野のエンジニアの人と話すときに「ViewModel」という言葉の意味が伝わってないなと感じるときもあります。
Reactエンジニアの方にiOSのソースコードを見てもらったときに 「このViewModelってのは、どんな意味があるんですか?」 って聞かれたこともあります。
Reactの世界では、fluxなアーキテクチャが主流になっていったので、SwiftUIでも同じ流れが来るのかなと思っています。(徐々にSwiftUIにおいても、flux的なアーキテクチャであるTCAを採用し始めているところも多いと聞きます)
MVVMを止めたときどうするの?
UIとロジックの分離について
MVVMを止めたときに 「ViewModelがなくなったあと、UIとロジックの分離はどうするの?」
という問題が、あります。
答えとしては、Model、もしくはFlux的なStore、もしくは、 "ドメインレイヤーの何か"に分離する のが良いと考えます。
中小規模のアプリであれば、MV(ModelとView)があれば、用は足ります。
ロジックを切り出すためにViewModelを作るのは、名前と責務が一致してないので、止めたいなと考えます。
前提として、設計上ViewModelレイヤーは、UI(プレゼンテーション)層に位置するレイヤーです。
Viewからロジックを切り出すために、ViewModelにロジックを切り出すことは、 UI固有のプレゼンテーション層のロジック を書くことになってしまい、ロジックの再利用性/変更容易性を妨げますし、 UIロジックとドメインロジックの境界線が曖昧 になります。
再利用されるビジネスロジックは、ドメインレイヤにカプセル化する必要がある と考えています。
ビジネスロジックを切り出す際
- Reactであれば、ReactHooks
- Vueであれば、CompositionAPI
- Flutterであれば、Provider/Riverpod
- JetpackComposeであれば、Composable関数
といった各種言語・フレームワークには再利用可能なロジックを切り出すために便利な仕組みが容易されているのですが、残念ながらSwiftUIにはこれらに相当する機能がありません。
SwiftUIでは、TCAというライブラリを使えば、Storeにビジネスロジックを切り出すことができるので、個人的にはTCAを使うのがよいと思っています。
宣言的UI時代に必要なアーキテクチャ
宣言的UIの登場で、 「モバイル開発といえば、MVVM一択」という時代は終わり 、混迷の時代に突入したのかなと思います。
現在、 SwiftUI開発でのデファクトスタンダードなアーキテクチャやベストプラクティスと呼べるような何かは存在せず、 そういったものが早く登場するのを待ち望んでます。
(もしすでにあれば教えていただけると助かります)
宣言的UI時代の大規模アプリ開発においては、僕らが本当に欲しかったものは、単方向データーフローもしくは、Flux(The Composable Architecture)やStore/Providerパターン なのではないでしょうか。
SwiftUIを使うことで、何もしなくてもMVVM相当のことはデフォルトで可能になったのですから、 もう一段上のレイヤーの問題を解決するアーキテクチャ が必要な時期になったとも言えます。
その問題とは、例えば 「コンポーネント設計をどうするか?」や「コンポーネントの状態とロジックをViewから切り出す仕組み」や「各コンポーネント間での状態管理の伝達の仕組み」 であったり、 「単方向データフロー」 だったりします。
MVVMアーキテクチャは、コンポーネント間の接続やデータフローの問題を解決するものではありません。
それらの解決方法を具体的に言うと、ReactだとFlux(Redux)やHooksだったり、FlutterだとProviderパターンやRiverpodだったりします。
iOS開発においても、宣言的UIによってもたらされた、MVVMの上位レイヤーの問題を解決するアーキテクチャやライブラリが、必要 だと考えます。
そもそもアーキテクチャの問題ではなくて、「サーバーから非同期にデータフェッチして、画面に表示したい」という非同期や状態管理の解決方法としては、
GraphQLクライアントのデータフェッチキャッシュライブラリ を使えば解決するかもしれません。
まとめ
最後に、結論として
ViewModelの存在は、宣言的UIの時代には必要ない と考えます。
ロジックの切り出し先として、ViewModelという言葉を使うのも止めたいなと思います。
これは、「私はこう考えた」という意味であって、「この考えが絶対的に正しい」という主張ではありません。
見ているコードベースや、バックグラウンド、考え方、観点、解釈の違いによって、その選択がよいのかどうか、様々だと思いますので。
(しかしながら、本記事に対する批判的なコメントは真摯に受け止めます。私の知識不足や説明不足により、お怒りや誤解を招いてしまい申し訳ありません)
また、「じゃあ、MVVMを止めて、〇〇のアーキテクチャを採用しよう」という、ある特定のアーキテクチャをオススメすることは、この記事では行いません。
(ぼく自身もまだ答えを出せていないです><)
TCA(The Composable Architecture)を採用することも検討しましたが、ちょっと大げさすぎるのが懸念です。。。どうしようかな。
(追記:検討した結果、TCAは時期尚早という意見が多いのですが、ぼく自身は一度使ってみたいと思っています)
この記事の続編として、以下の記事を投稿しましたので合わせて御覧ください。
https://qiita.com/karamage/items/f63a5750e65c5c9745ae
The Composable Architecture
https://github.com/pointfreeco/swift-composable-architecture
しかしながら、そのアプリに合ったアーキテクチャを ”よく考えて” 採用することをオススメします。
仮に考えた結果が 「やっぱりMVVMを採用しよう」 であったとしても、それはそれで良いと考えて、その考えを否定するつもりは一切ありません。
あくまで、 私個人が、SwiftUIを使うにあたって「MVVMを使うのは止めよう」 という考えに至ったので、その理由について書き記したという意図ですので、MVVMをディスるつもりはなく、現状の「何も考えずにMVVMを採用する」流れはよくないなと考えた末、この文章をしたためました。
最後まで読んでいただき、ありがとうございました。
もし、ご意見ありましたら、コメント欄に書き込んでいただけると嬉しいです。
追記
誤解を招いたかなと思いましたので、追記しておきます。
誤解されてるなと感じたツイート
「ViewModel = ObservableObject」
「つまりObservableObjectを使わないってこと?」
「@Stateだけ使えってこと?」
と、誤解されている投稿 を見かけましたので、追記させていただきます。
「 SwiftUIのObservableObjectは、ViewModelそのもの やん」というのは、まったくそのとおりでもあり、別物でもあります。
この、 「ObservableObjectは、ViewModelです」というのは、一体、誰が決めたことなのでしょうか?
SwiftUIにおいて、MVVMアーキテクチャではないのに、 ObservableObjectにViewModelという名前を付けて、乱用されているケースもしばしば目にします。
(ObservableObjectにViewModelという名前を付けること=MVVMだと思っている方も多い…)
この場合のViewModelは、「ViewModel」って名前を付けておきながら、MVVMのViewModelとはまったく関係ない、ただの 「状態ホルダー兼ロジック置き場」 となっています。
SwiftUIのObservableObjectにViewModelという名前を付けると、なんでもかんでもViewModelに責務を詰め込もうとしてViewとの1:1の関係が壊れたり、単一責任原則が破られて無秩序化しやすいです。
これは過去のMVVMの栄光に、脳内が縛られてしまっている状態です。
一方で、 ObservableObjectは、"ただの"リアクティブなObject であって、ViewModelではない、と考えることもできます。
Appleの公式Docに、「ObservableObjectは、ViewModelです」とは、 一言も書かれていません。
Appleは、SwiftUIの推奨アーキテクチャについて、言及していません。
Apple のWWDCの動画を観ても、MVVM/ViewModelという単語が出てくることはありません。
「ObservableObjectは、ViewModel」というのは、誤って広まった慣習であり、これを "常識" とみなすのには反対です。
ObservableObjectは、単なるModel/StateHolderと考えるのが自然ではないでしょうか。
しかしながら、SwiftUI.ObservableObjectは、SwiftUIの一部であり、どんどん使えばよいと思います。
本記事は 「ObservableObjectを使うのをやめよう」という意図はない です。
ご理解いただけると幸いです。
ちなみに、そのObservableObjectにViewModelという名前をつけることに対しては良くないと思っていて、
Model(State)名/Store名/Domain名/UseCase名等をそのまま付けるのが良いかなと考えています。
(※ViewModelレイヤーを抜く)
monoさんの説明がわかりやすいので引用(Flutterの例ですけど)
関連ツイートまとめ
以下、本記事に関連するツイートを引用させていただきます。
多くの反響があり、驚いています。
みなさまのご意見、学びになりました。嬉しいです。
とてもわかりやすい資料です。ありがとうございます。
補足ありがとうございます。WPFについて知識不足でした。
同意です。
同意見です。ただ、それにViewModelと名付けるのは逸脱しすぎてるのかなと感じます。
ぼくもFlutterでのMVVM、違和感あります。
同意です。
本記事は、「ObservableObjectを使うな」という意図はないです。誤解させてしまって申し訳ないです。
実際ためしてみての知見ありがたいです。
本記事が間違っていました。申し訳ありません。
ぼくもTCAの採用には二の足を踏みます。
まさにその通りです。
激しく同意です!
MVVM、おぉMVVM…
今回の騒動の学びをご照覧あれ…
本記事の英語版はこちら(かなり反響がありました)
https://medium.com/@karamage/stop-using-mvvm-with-swiftui-2c46eb2cc8dc