iOSエンジニアリング

[iOSアーキテクチャ #3] SwiftUIにViewModelは不要? MVパターン論争まとめ

iOSコミュニティでは、何年も続いている熱い議論があります。

読了 5 分
[iOSアーキテクチャ #3] SwiftUIにViewModelは不要? MVパターン論争まとめのカバー画像

iOSコミュニティでは、何年も続いている熱い議論があります。

「SwiftUIにViewModelは必要?」

前回は、MVVMの核心はバインディングだと整理しました。SwiftUIにはバインディングがフレームワークに組み込まれています。そこで「ViewModelという層自体が重複なのでは」という主張が出てきました。いわゆるMV(Model-View)パターン派です。

シリーズ最終回となる今回は、双方の論拠を公平に検討し、実際に判断するための基準まで整理します。


SwiftUIのViewはすでにViewModelに似ている

UIKitでMVVMが必要だった理由を振り返ると、次のとおりです。

  • UIViewControllerが肥大化したため、状態とロジックを外に出したかった
  • 状態と画面を同期するバインディングが必要だった

しかしSwiftUIのViewは、そもそも状態の関数です。body状態を受け取り画面宣言を返すだけで、@State値が変わればフレームワークが自動的に再描画します。バインディングのためにCombineの購読コードを書く必要はありません。

さらにSwiftUIのViewはクラスではなく、値型のstructです。UIViewControllerのように数千行へ肥大化するライフサイクルの塊ではなく、レンダリングごとに作り直される軽量な設計図に近い存在です。

MV派の主張はここから始まります。Viewも肥大化せず、バインディングも無料なのに、画面ごとにViewModelクラスを作るのはUIKitの習慣を惰性で持ち込んでいるだけではないか、というわけです。


MV派の論拠

MVパターンでは画面ごとにViewModelを置かず、Viewが状態管理ツールを直接使います。

  • 1つのViewに閉じた状態は@Stateそこに置く
  • 複数画面で共有する状態は@Observableモデルとして@Environment注入する
  • サーバーデータはModel層のstoreやclientが担当する
struct ProfileView: View {
    @Environment(UserStore.self) private var store
    @State private var isEditing = false

    var body: some View {
        List(store.posts) { PostRow(post: $0) }
            .task { await store.loadPosts() }
    }
}

Appleの公式サンプルコード(Fruta、Food Truckなど)も、おおむねこの構造です。画面ごとのViewModelではなく、ドメイン単位の@Observableモデルを複数のViewで共有します。

この方式の利点は明確です。画面ごとにクラスを作るボイラープレートがなくなり、@State@Bindingといったフレームワークのツールを迂回せず直接使えます。

SwiftUIのViewはそもそも状態の関数です
SwiftUIのViewはそもそも状態の関数です

MVVM派の反論

反対側の論拠も決して軽くありません。

第一に、ロジックがViewに入り込みます。「投稿が0件で、読み込みが終わり、エラーがなければ案内文を表示する」といった条件がbody内に三項演算子として積み重なると、値型であっても読みにくくなります。

**第二に、テストの問題です。**Viewのbodyはユニットテストで実行しにくいものです。プレゼンテーションロジックがViewにあると、そのロジックもテストの盲点になります。ViewModelに分離すれば純粋なSwiftオブジェクトとしてすぐ検証できます。

**第三に、規模の問題です。**画面が数十個あり複数人で開発するアプリなら、状態とロジックの場所について一貫したルールが必要です。画面ごとにViewModelという決まった場所があるほうが、協業コストを下げられるという主張です。


名前ではなく依存方向

双方の論拠を見ると、この論争は「ViewModelというクラスがあるか」より、ロジックと状態がどこにあるかという問題です。この基準で考えれば、ほとんどのケースを整理できます。

  • 1つのViewに閉じたUI状態(トグル、フォーカス、シートの表示状態)は@StateViewに置きます。これをViewModelに出すのは過剰設計です。
  • 複数画面で共有するドメイン状態@Observableモデルとして作り、注入します。Storeと呼ぶかViewModelと呼ぶかは本質ではありません。
  • 画面用の加工ロジックが複雑になったら(フォーマット、表示条件、並べ替え・フィルタリング)、Viewの外にあるテスト可能な型へ分離します。それが実質的なViewModelです。
  • いずれにせよ、ViewがネットワークやDBに直接触れないようにするという依存方向を守れば、MVかMVVMかは名前の違いに近いものです。

結局、「SwiftUIにViewModelは不要」という言葉の実態はこう要約できます。画面ごとに機械的にViewModelを作る必要はない。ただし、Viewの外に出すべきロジックがなくなるわけではない。

名前ではなく依存方向が本当の判断基準です
名前ではなく依存方向が本当の判断基準です

シリーズまとめ

3回にわたるiOSアーキテクチャシリーズを、各1行に圧縮すると次のとおりです。

  • MVC:UIViewControllerがViewとControllerを兼ねるため、責務が集中する。小規模な画面では今も有効です。
  • MVVM:画面状態をViewModelに分離し、バインディングで同期する。バインディングがなければ不完全です。
  • MV論争:SwiftUIにはバインディングが組み込まれているため、画面ごとにViewModelを強制する理由はない。ただし、ロジックの置き場所と依存方向は依然として設計が必要です。

アーキテクチャは流行ではなく、チームとアプリの規模に合わせたトレードオフの選択です。反響があれば、VIPERやTCAのような、より重い選択肢も続けて扱います。

あわせて読みたい