MVC(Model-View-Controller)は、アプリケーションを3つのコンポーネントに分割するアーキテクチャパターンです。Modelはデータとビジネスロジックを担当し、Viewはユーザーインターフェースを担当し、Controllerは入力処理とModelとViewの調整を担当します。iOSではMVCはUIViewControllerを通じて実装され、AndroidではActivityとFragmentを通じて実装されます。MVCはMVVM、MVP、Clean Architectureが構築される基礎となるパターンであり続けています。詳細はMVC in Cocoa Coreをご覧ください。
重要なポイント
MVC(Model-View-Controller)は、1979年にTrygve ReenskaugがSmalltalk-80言語向けに提案したアーキテクチャパターンです。このパターンはアプリケーションを3つの層に分割します:Modelはデータとビジネスロジックを含み、Viewは表示を担当し、Controllerはユーザー入力を処理してModelとViewを更新します。責任の分離により、各層を独立して変更できます。例えば、Modelのビジネスロジックを変更せずにViewをUIKitからSwiftUIに置き換えることができます。
コンポーネントの相互作用はMVCで次のサイクルに従います:ユーザーがViewと対話する → Controllerがイベントを受け取る → ControllerがModelを更新する → ModelがControllerに変更を通知する → ControllerがViewを更新する。古典的な実装では、ModelはObserverパターンを使用します:データが変更されると、Modelが通知を送信し、Controllerが購読してViewを更新します。Appleの実装では、Key-Value Observing(KVO)またはNotificationCenterがこの役割を果たします。
| コンポーネント | 責任 | iOSの例 | Androidの例 |
|---|---|---|---|
| Model | データ、ビジネスロジック、ネットワーク | Struct User、CoreData | Data class、Repository |
| View | UI表示 | Storyboard、XIB、UIView | XML layout、Jetpack Compose |
| Controller | 入力処理、調整 | UIViewController | Activity、Fragment |
現代のモバイル開発におけるMVCは10年前よりも使用頻度は減りましたが、理解することは依然として不可欠です。AppleはUIKitアプリケーションのシンプルな画面にMVCを推奨しています。GoogleはAndroidに純粋なMVCを推奨していません — 公式ドキュメントはJetpackを使ったMVVMを提案しています。しかし、レガシープロジェクトでの作業やアーキテクチャパターンの進化を理解するためには、MVCの知識が必要です。
Apple MVCはUIKitに組み込まれたパターンのカスタム実装です。UIViewControllerはControllerとして機能します:画面のライフサイクル(viewDidLoad、viewWillAppear、viewDidDisappear)を管理し、タッチやユーザーアクションを処理し、IBOutletsを通じてViewを更新します。ViewはInterface Builder(storyboardまたはXIB)またはプログラムmaticallyに作成されます。Model — ネットワークサービス、CoreDataスタック、Swift構造体など、任意のデータオブジェクトです。
final class UserViewController: UIViewController {
// View(storyboard outlet経由)
@IBOutlet private var nameLabel: UILabel!
@IBOutlet private var emailLabel: UILabel!
// Model
private let userService = UserService()
override func viewDidLoad() {
super.viewDidLoad()
loadUser()
}
private func loadUser() {
userService.fetchUser { [weak self] user in
// ControllerがViewを更新
self?.nameLabel.text = user.name
self?.emailLabel.text = user.email
}
}
}
Apple MVCの問題 — ViewとControllerが密結合です。UIViewControllerはViewとロジックの両方を同時に管理します。StoryboardはViewをXMLに保存しますが、コントローラはIBOutletsを通じてUI要素への直接参照を持ちます。これは単一責任の原則に違反します:コントローラはライフサイクル、デリゲート、データソース、target-action、アニメーションに責任を持ちます。その結果、標準的なiOSアプリの画面にはコントローラに200〜500行が含まれます。
ViewControllerのライフサイクル — Appleは6つのライフサイクルメソッドを提供します:loadView(手動でのView作成)、viewDidLoad(メモリへのViewロード後)、viewWillAppear(画面表示前)、viewDidAppear(アニメーション後)、viewWillDisappear(画面離脱前)、viewDidDisappear(離脱後)。各メソッドはMVCにロジックを配置する場所です。これらのメソッドをビジネスロジックに使用すると、コントローラの肥大化が加速します。
Android MVC — ActivityとFragmentがControllerとして機能し、XML layoutファイルがViewとして、データを持つPOJOクラスがModelとして機能します。Activityは画面のライフサイクルを管理します:onCreate、onStart、onResume、onPause、onStop、onDestroy。Fragmentは独自のライフサイクルを持つActivity内のサブ画面です。View(XML)はControllerから分離され、setContentViewまたはLayoutInflaterを介してロードされます。Model — リポジトリ、データベース、ネットワーク呼び出し。
class UserActivity : AppCompatActivity() {
// View(XML layout経由)
private lateinit var binding: ActivityUserBinding
// Model
private val userRepository = UserRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserBinding.inflate(layoutInflater)
setContentView(binding.root)
loadUser()
}
private fun loadUser() {
userRepository.getUser { user ->
runOnUiThread {
binding.nameText.text = user.name
binding.emailText.text = user.email
}
}
}
}
Android ViewBindingとDataBinding — ControllerとViewの間の結合を減らす最新ツールです。ViewBindingはXMLからViewsへの直接参照を持つクラスを生成し、findViewByIdを不要にします。DataBindingは@{user.name}を使用してXMLマークアップでデータをUIにバインドする機能を追加します。DataBindingはMVVMへの一歩であり、ActivityのコードなしでModelからViewにデータを渡すことができます。Googleはすべての新しいプロジェクトにDataBindingを推奨しています。
AndroidのライフサイクルはiOSより複雑です:画面回転、メモリ不足、設定変更時にActivityが破棄され再作成される可能性があります。純粋なMVCでは、コントローラ(Activity)には破棄時に失われるロジックが含まれます。これにはonSaveInstanceStateまたはJetpackのViewModelを介した状態保存が必要であり、純粋なMVCを超えてアーキテクチャをMVVMに近づけます。
Massive View Controllerは、モバイル開発におけるMVCの主な問題を表す用語です。iOSとAndroidのコントローラはあまりにも多くの責任を負います:入力処理、データ検証、ネットワーク通信、ナビゲーション、キャッシング、アニメーション、ライフサイクル管理。その結果、コントローラは500〜2000行のコードに膨れ上がり、読み取り、テスト、保守が困難になります。
Massive View Controllerの原因 — UIKitとAndroid Frameworkのアーキテクチャは、コントローラにロジックを配置することを促進します。ネットワーク呼び出し、JSON処理、ナビゲーション — これらすべては自然にActivityまたはUIViewControllerに配置されます。なぜなら、それらはライフサイクルとUIにアクセスできるからです。開発者は意識的にロジックを別のクラス(Service、Manager、Interactor)に抽出する必要があり、これには規律とアーキテクチャ原則の理解が必要です。
| MVCの問題 | 説明 | 解決策 |
|---|---|---|
| 密結合 | ControllerがViewとModelを知っている | MVVM — ViewModelはViewを知らない |
| テストの複雑さ | ControllerがUIKit/Androidに依存 | サービスへのロジック抽出 |
| ライフサイクル | 回転時に状態が失われる | Jetpack/SwiftUIのViewModel |
| ナビゲーションの欠如 | Controllerが遷移を管理 | Coordinatorパターン、Router |
MVCのテスト — Modelはユニットテストで分離してテストされます。ControllerはUIKit/UIFoundationに依存するためテストが困難です。XCTestはビューウィンドウなしでUIViewControllerを作成できません。Androidでは、ActivityTestRuleとRobolectricが部分的に問題を解決しますが、テストは遅くなります。Viewは通常ユニットテストでテストされません — UIにはスクリーンショットテストとUIテスト(XCUITest、Espresso)が使用されます。
MVCが正当化される場合 — 1つまたは2つの要素を持つシンプルな画面(ログイン画面、プロフィール、設定)。仮説検証のためのプロトタイプとMVP — MVCは追加の層なしで迅速に記述できます。10〜15画面までの小規模コードベースのプロジェクト。複雑なプロジェクトでは、MVCは技術的負債の蓄積につながり、6〜12ヶ月ごとにリファクタリングが必要です。
MVC vs MVVM — 主な違い:MVVMでは、コントローラはViewへの参照を持たないViewModelに置き換えられます。データはObservable(SwiftUI)、LiveData/StateFlow(Android)、またはCombine/RxSwiftを介して渡されます。ViewModelはUI依存関係なしでユニットテストでテスト可能です。Appleは2019年からSwiftUIでのMVVMを推奨し、GoogleはLiveData/Flowを使用したMVVMを公式なAndroidアーキテクチャとして推奨しています。MVVMはバインディングにより多くのコードを必要としますが、テスト容易性を大幅に向上させます。
MVC vs MVP — MVP(Model-View-Presenter)では、Presenterはインターフェースを介してViewを受け取るテスト可能な層です。ControllerがUIKitを介して直接Viewを管理するMVCとは異なり、Presenterはフレームワークに依存せず、ViewInterface抽象化を通じて機能します。MVPはJetpack以前のAndroid開発で人気があり、レガシープロジェクトで使用されています。PresenterはActivityより長く存続し、画面回転時に状態を保持します。
MVC vs Clean Architecture — Clean Architectureは層を追加します:Use Cases(Interactors)、Entities、Gateways、Repository。MVCはPresentation層に残りますが、ビジネスロジックはUse CasesとともにDomain層に移動します。Clean ArchitectureはMassive View Controller問題を根本的に解決します — ControllerはUse Casesの呼び出しとViewの更新のみを含みます。欠点はクラスとファイルの数が大幅に増加することで、50画面以上のプロジェクトでは正当化されます。
// iOSのMVC:Controllerがすべてを含む
class OrderViewController: UIViewController {
func placeOrder() {
// 検証 + ネットワーク + UI更新
guard Validation.isValid(total) else { return }
NetworkService.shared.submit(order) { [weak self] result in
self?.handleResult(result)
}
}
}
// MVVM:ViewModelにロジック
class OrderViewModel: ObservableObject {
@Published var state: OrderState = .idle
func placeOrder() { /* ビジネスロジック */ }
}
アーキテクチャの選択はチームの規模、プロジェクトの範囲、必要なテスト容易性に依存します。1〜2人の開発者のチームと最大20画面のプロジェクトには、MVVMが適しています。5人以上の開発者の大規模チームと50画面以上のプロジェクトには、モジュール構造のClean Architectureが適しています。MVCは今でも重要です — アーキテクチャの進化を理解するため、レガシープロジェクトを保守するため、複雑なビジネスロジックのないシンプルなUIKit画面のためです。
よくある質問
主な問題はMassive View Controllerです。iOSでは、UIViewControllerがすべてを処理します:入力処理、Viewの更新、ネットワーキング、ナビゲーション、ライフサイクル。Androidでは、Activity/Fragmentが同様の機能を実行します。その結果、コントローラは数千行のコードに膨れ上がり、テストと保守が困難になり、単一責任の原則に違反します。
MVCでは、コントローラが直接Viewを更新し、ユーザー入力を処理します。MVVMでは、コントローラの役割をViewModelが果たし、Viewへの参照を持ちません — データはバインディングメカニズムを介して渡されます。MVVMはViewModelがUIKitやAndroid Frameworkに依存しないため、テストが容易です。AppleはSwiftUIでのMVVMを推奨し、GoogleはJetpack ComposeでのMVVMを推奨しています。
はい、MVCはシンプルな画面やプロトタイプには依然として機能的なパターンです。Appleはシンプルな画面のUIKitアプリケーションにMVCを推奨しています。多くの画面、ネットワークリクエスト、キャッシングを伴う複雑なプロジェクトには、MVVM、VIPER、またはClean Architectureを選択する方がよいでしょう。初心者の開発者は、より複雑なパターンを学ぶ前にMVCを習得することをお勧めします。
Modelは分離してテストされます — これらは通常のデータオブジェクトとビジネスロジックです。ControllerはUIKitやAndroid Frameworkに依存するためテストが困難です。コントローラからビジネスロジックを別のサービスやインタラクターに抽出することをお勧めします。これらはユニットテストでテストされます。Viewは通常ユニットテストでテストされません — UIテストとスクリーンショットテストが使用されます。
iOSでは — SwiftUIとCombineを使用したMVVM、2019年からのAppleの標準です。Androidでは — LiveDataまたはStateFlowを使用したMVVM、Googleが公式に推奨しています。5人以上の開発者の大規模プロジェクトでは — iOSではVIPERを使用したClean Architecture、Androidでは機能ベースのモジュール分割を使用したClean Architecture。MVCのレガシープロジェクトでは — 別のサービスへのロジック抽出を伴う段階的なリファクタリング。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。