iOSエンジニアリング

[iOSアーキテクチャ #1] iOS MVCパターンとMassive View Controllerが生まれる本当の理由

iOS開発をしていると、一度は耳にするジョークがあります。

読了 5 分
[iOSアーキテクチャ #1] iOS MVCパターンとMassive View Controllerが生まれる本当の理由のカバー画像

iOS開発をしていると、一度は耳にするジョークがあります。

「MVCはModel-View-Controllerではなく、Massive View Controllerの略だ」

冗談ですが、核心を突いています。Appleが公式に推奨するアーキテクチャなのに、なぜ従うとビューコントローラーが数千行の怪物になるのでしょうか?

iOSアーキテクチャシリーズ第1回として、MVC本来の姿と、iOSでその通りにならない理由を整理します。

結論から言えば、問題はMVCではなく、UIViewControllerがViewとControllerの境界にまたがる構造そのものです。


MVCは本来どのような構造だったのか

MVCは1979年にSmalltalkから生まれた、非常に古いパターンです。原型では3つの役割が明確に分かれていました。

  • Model:データとビジネスロジック
  • View:画面表示
  • Controller:ユーザー入力を受け取り、Modelに渡す

原型のMVCでは、ViewがModelを直接監視します。Modelが変わると、Viewが自動的に更新される仕組みです。

一方、AppleのMVCは少し異なります。ViewとModelを互いに知らない状態にし、Controllerが中央ですべてを仲介する構造に変えました。再利用性を高める合理的な選択でしたが、Controllerに仕事が集中する余地も生まれました。


UIViewControllerは名前からしてルール違反

Apple MVCのControllerは、iOSではUIViewControllerとして実装されます。しかし、もう一度クラス名を見てください。View + Controllerです。名前からして2つの役割が結合しています。

実際、UIViewControllerは次の仕事をすべて担当します。

  • viewDidLoadviewWillAppearなどのビューライフサイクル管理
  • 画面回転やレイアウト更新など、ビューに密着したイベントの処理
  • UITableViewDataSourceUITableViewDelegateなどのビュープロトコルの実装

ここまではまだビュー関連の仕事です。問題は、「ではネットワークリクエストはどこに書く?」「画面遷移は?」「データのフォーマットは?」という問いに明確な答えがないことです。ModelでもViewでもないため、結局すべてビューコントローラーに入ります。

名前からしてView + Controller。1人ですべてを担うのが基本です。
名前からしてView + Controller。1人ですべてを担うのが基本です。

肥大化したビューコントローラーの典型例

よくあるビューコントローラーの構造をコードで要約すると、次のようになります。

final class ProfileViewController: UIViewController {
    // 1. ビューのプロパティ
    private let tableView = UITableView()

    // 2. 状態(実質的には Model キャッシュ)
    private var user: User?
    private var posts: [Post] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        setupLayout()          // 3. レイアウトコード
        fetchProfile()         // 4. ネットワークリクエスト
    }

    private func fetchProfile() {
        URLSession.shared.dataTask(...) { ... }  // 5. パースとエラー処理
    }

    @objc private func editTapped() {
        // 6. 画面遷移まで直接担当
        navigationController?.pushViewController(EditViewController(), animated: true)
    }
}

レイアウト、状態管理、ネットワーク、パース、画面遷移が1つのファイルにすべて入っています。さらにdelegateの実装まで加わると、すぐに1000行を超えます。

この構造の本当のコストは行数ではなく、テストできなくなることです。「userがnilなら編集ボタンが隠れる」という単純なロジックを検証するだけでも、UIViewControllerを丸ごと起動してライフサイクルを再現する必要があります。


ではMVCは捨てるべきか?

そうとは限りません。小規模な画面では、MVCは今でも最速でシンプルな選択肢です。Appleのフレームワーク自体がMVCを前提に設計されているため、無理に別の構造を重ねるとかえって摩擦が生じます。

重要なのは、MVCを使う場合でも、ビューコントローラーから切り離せる責務を意識的に切り離すことです。

  • ネットワーク・データロジック → 独立したサービスオブジェクトへ
  • 画面遷移 → Coordinatorなどの専用オブジェクトへ
  • セル構成・フォーマット → 専用の型へ

そして、「画面に表示する状態を作るロジック」まで分離したくなる時が来ます。その答えが、次回扱うMVVMです。ViewModelがなぜ必要なのか、そしてバインディングがなければなぜ不完全なのかを続けて整理します。

切り離せる責務を意識的に分離することが、最初の一歩です。
切り離せる責務を意識的に分離することが、最初の一歩です。

まとめ

  • MVC自体に罪はありません。問題は、UIViewControllerがViewとControllerの両方を担うという、iOSの構造的な特性です。
  • ModelでもViewでもないコードの行き場がなく、すべてがビューコントローラーに積み上がる。それがMassive View Controllerの正体です。
  • 小規模な画面ではMVCは今でも有効です。ただし、ネットワーク、画面遷移、フォーマットは意識的に分離すべきです。
  • ビューの状態を作るロジックまで分離するにはMVVMが必要です。次回扱います。

あわせて読みたい