131
Help us understand the problem. What are the problem?

posted at

updated at

「SwiftUIでMVVMを採用するのは止めよう」と思い至った理由

※2022/04/23 追記

本記事の続編として、以下の記事を書きましたので、合わせて御覧ください。

仕事でSwiftUIでTCAを使ってみて、かなり知見がたまったので、その解説です。
MVVMからTCAへの移行を考えているのであれば、参考になると思います。

宣言的UIに、MVVMって不要なのでは?

iOS開発の現場で、宣言的UIが当たり前に使われるような時代になりました。

SwiftUIの開発体験、素晴らしい です。最高です。

Screen Shot 2022-03-12 at 15.37.24.png

しかし最近、SwiftUIで当たり前のように 「MVVMで開発しよう」 となったときに、
「ほんとにそれでいいんだっけ?」 と疑問に思いました。

question_head_boy.png

自分の考えを深掘ってみると

  • 問い:

    • 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
https://developer.apple.com/forums/thread/699003

Is MVVM an anti-pattern in SwiftUI?
https://www.reddit.com/r/swift/comments/m60pv7/is_mvvm_an_antipattern_in_swiftui/

STOP using MVVM for SwiftUI | Clean iOS Architecture
https://www.youtube.com/watch?v=SOA0IT7sxvc

Stop using MVVM with SwiftUI(本記事の英語版)
https://medium.com/@karamage/stop-using-mvvm-with-swiftui-2c46eb2cc8dc

SwiftUIでViewModelを使うのは、
「ホバーボードで空を飛べるのに、わざわざ車輪をくっつけて地べたを走ってるようなものだ」
という皮肉の効いた投稿もなされています。

Screen Shot 2022-03-12 at 13.22.54.png

なぜ、われわれは、MVVMを選択してしまうのか

最近、iOS開発でSwiftUIを使う機会が増えました。

SwiftUIを使ったアプリで、アーキテクチャをどうするか問題に頭を悩ませています。

モバイルアプリ開発をしていると、なぜかアーキテクチャは、MVVMを選択しがち です。

ぼくは 「なぜっ?」 って疑問に思います。

これは、モバイルアプリ開発の古くからの伝統で、
2016年にGoogleがAndroid開発において、MVVMを提唱することから始まっています。(※あくまで提唱しただけ)(※訂正 大元の発祥はMicrosoftらしいです)
元々はAndroid開発の現場からMVVMの採用が始まったのですが、それをiOSの開発にも持ち込んで当たり前のように広まってしまったのが現状です。

しかしながら、
「Google が推奨するアーキテクチャは MVVM だ」
と言った事実は存在しません。(自分の探した限りでは)

現在、Google公式のアーキテクチャガイドから、「MVVM」という言葉は消えています。
※「ViewModel」という言葉は微妙に残っていていますが、ぼくは正確な意味が汲み取れていません。

しかもMVVMの発祥はAndroid開発であって、(※間違いです。発祥はMicrosoftです。)
そもそも、本家Appleは、MVVMという言葉を使ったことすらありません。

Googleも 「最終的に、Jetpack Composeでは、MVVM/ViewModelを無くしたい」 という発言が見え隠れするので、Android開発でもMVVMというワードが消えていくのかなと雰囲気を感じ取っています。(※個人の感想です)

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

Screen Shot 2022-03-12 at 15.37.24.png

宣言的UIの登場で、ViewModelは、存在理由を失っている

つまり、 SwiftUIにViewModelのデータバインディング機能が内包された時点で、 ViewModelは存在理由を失い、「ViewModel」という言葉自体も意味がよくわからない言葉 になってしまいました。

ViewModelレイヤーでやるべきことが何なのか明確化されてないので、とりあえず面倒事は全部押し付けられがちです。本来、別のレイヤーですべきこともViewModelにつっこんでたりします。

MVVMを採用すると余計な複雑性を生んでしまう。

SwiftUIでMVVMを採用してしまうと「ViewModel」という余計なレイヤーを挟んでしまうため、複雑になります。

データフローについて、ViewとModelがViewModelという中間レイヤーを挟んだ双方向データフローになってしまいます。

Apple公式でも示されている、単方向データフローとは乖離することを、懸念しています。

State-and-Data-Flow-1@2x.png

宣言的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で運んでいるNotifierの名前"「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を作るのは、名前と責務が一致してないので、止めたいなと考えます。

宣言的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です」というのは、一体、誰が決めたことなのでしょうか?

ObservableObjectは、"ただの"リアクティブなObjectであって、ViewModelではない、と考えることもできます。

Appleの公式Docに、「ObservableObjectは、ViewModelです」とは、一言も書かれていません。

「ObservableObjectは、ViewModel」というのは、誤って広まった習慣であり、これを "常識" とみなすのには反対です。

ObservableObjectは、単なるModel/StateHolderと考えるのが自然ではないでしょうか。

しかしながら、SwiftUI.ObservableObjectは、SwiftUIの一部であり、どんどん使えばよいと思います。
本記事は 「ObservableObjectを使うのをやめよう」という意図はない です。
ご理解いただけると幸いです。

ちなみに、そのObservableObjectにViewModelという名前をつけることに対しては、
Model名/Store名/Domain名/ユースケース名等をそのまま付けるのが良いかなと考えています。
(ViewModelレイヤーを抜く)

monoさんの説明がわかりやすいので引用(Flutterの例ですけど)

関連ツイートまとめ

以下、本記事に関連するツイートを引用させていただきます。
多くの反響があり、驚いています。
みなさまのご意見、学びになりました。嬉しいです。

とてもわかりやすい資料です。ありがとうございます。

補足ありがとうございます。WPFについて知識不足でした。

同意です。

同意見です。ただ、それにViewModelと名付けるのは逸脱しすぎてるのかなと感じます。

ぼくもFlutterでのMVVM、違和感あります。

同意です。

本記事は、「ObservableObjectを使うな」という意図はないです。誤解させてしまって申し訳ないです。

実際ためしてみての知見ありがたいです。

本記事が間違っていました。申し訳ありません。

ぼくもTCAの採用には二の足を踏みます。

まさにその通りです。

激しく同意です!

MVVM、おぉMVVM…
今回の騒動の学びをご照覧あれ…

本記事の英語版はこちら(かなり反響がありました)
https://medium.com/@karamage/stop-using-mvvm-with-swiftui-2c46eb2cc8dc

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
131
Help us understand the problem. What are the problem?