VIPER: 主要概念、View-Interactor-Presenter-Entity-Routerパターン

著者: IT Sectr 公開日: 2026-02-16 読了時間: 9 分

VIPER(View-Interactor-Presenter-Entity-Router)— Mutual Mobile社がiOSアプリケーション向けに開発したモジュラーアーキテクチャです。VIPERはアプリケーションを5つの層に分割します。Viewは表示、Interactorはビジネスロジック、Presenterはデータ準備、Entityはデータモデル、Routerはモジュール間のナビゲーションを担当します。VIPERはモバイルアーキテクチャの中で単一責任原則を最も詳細に実装したものです。詳細はobjc.ioの記事をご覧ください。

重要ポイント

  • VIPER — 5つのコンポーネント:View、Interactor、Presenter、Entity、Router(明確な責任境界を持つ)
  • モジュール性 — 各画面(モジュール)は分離され、プロトコルを介して通信
  • Router — Presenterからナビゲーションを切り離し、iOSのナビゲーション問題を解決
  • Interactor — ビジネスロジックを含み、UIKitに依存せず、単体テストでテスト可能
  • iOSネイティブ — VIPERはSwiftUI登場前にUIKit用に作られ、大規模iOSプロジェクトの標準であり続けている

VIPERとは:モジュラーアーキテクチャの5つのコンポーネント

VIPER(View-Interactor-Presenter-Entity-Router)— 2013〜2014年にMutual Mobileで大規模iOSプロジェクト向けに開発されたアーキテクチャパターンです。アプリケーションの各画面は、厳密に定義された責任を持つ5つのコンポーネントからなる独立したモジュールです。VIPERはモバイル開発における単一責任原則(Single Responsibility Principle)の最も厳格な実装です。どのコンポーネントも、他のコンポーネントができることを行いません。

Viewはパッシブなコンポーネントで、Presenterから渡されたデータの表示のみを担当します。Viewはビジネスロジックを含まず、ナビゲーションを処理せず、ネットワークリクエストを行いません。iOSでは — ViewProtocolを持つUIViewControllerです。InteractorはEntityやサービス(ネットワーク、DB、GPS)と連携するビジネスロジック層です。InteractorはUIKitをインポートしません。PresenterはViewとInteractorの間の仲介者です。Interactorからデータを受け取り、表示用にフォーマットしてViewに渡します。PresenterもUIKitをインポートしません。Entity — データモデル(struct、class)。Router — ナビゲーションを管理し、モジュールを作成し、画面を開き、モジュール間でデータを渡します。

コンポーネント責任依存関係
View表示、アニメーション、ジェスチャーUIKit(Viewのみ)
Interactorビジネスロジック、ネットワーク、DBEntity、サービス
Presenterデータフォーマット、ViewコマンドViewProtocol、Interactor
Entityデータモデルなし
Routerナビゲーション、モジュール作成UIViewController(遷移用)

コンポーネント間の関係はプロトコルによって記述されます。ViewProtocolは表示メソッドを、InteractorProtocolはビジネスロジックメソッドを、PresenterProtocolはイベント処理メソッドを、RouterProtocolはナビゲーションメソッドを定義します。各コンポーネントはプロトコルを介してのみ通信し、実装の容易な置き換えと分離テストを可能にします。平均して、1画面のVIPERモジュールには5つのプロトコル+5つのクラス+1つのBuilder/Assembler=11ファイルが含まれます。

SwiftのVIPER:モジュール、Router、Presenter

VIPERモジュールの構築はBuilder(またはAssembler)で行われ、5つのコンポーネントすべてを作成し、プロトコルを介して接続します。Builderはコンポーネントが互いの具象型を知っている唯一の場所です。アセンブリ後、Viewは表示のために外部に返され、残りのチェーンはクリーンで分離してテストされます。

swift
// プロトコル — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

// プロトコル — Interactor
protocol UserInteractorProtocol {
    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}

// プロトコル — Router
protocol UserRouterProtocol {
    func navigateToProfile(userId: Int)
}

// Interactor — ビジネスロジック
final class UserInteractor: UserInteractorProtocol {
    private let service: UserService

    init(service: UserService) { self.service = service }

    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
        service.fetchUser(id: id, completion: completion)
    }
}

// Presenter — データ準備
final class UserPresenter {
    private weak var view: UserViewProtocol?
    private let interactor: UserInteractorProtocol
    private let router: UserRouterProtocol

    init(interactor: UserInteractorProtocol, router: UserRouterProtocol) {
        self.interactor = interactor
        self.router = router
    }

    func setView(_ view: UserViewProtocol) {
        self.view = view
    }

    func viewDidLoad() {
        view?.showLoading()
        interactor.fetchUser(id: 42) { [weak self] result in
            guard let self else { return }
            self.view?.hideLoading()
            switch result {
            case .success(let user):
                self.view?.display(name: user.name)
                self.view?.display(email: user.email)
            case .failure(let error):
                // エラー処理
            }
        }
    }
}

// Router — ナビゲーション
final class UserRouter: UserRouterProtocol {
    private weak var viewController: UIViewController?

    func setViewController(_ vc: UIViewController) {
        viewController = vc
    }

    func navigateToProfile(userId: Int) {
        let profileModule = ProfileModuleBuilder.build(userId: userId)
        viewController?.navigationController?.pushViewController(profileModule, animated: true)
    }
}

// Builder — モジュール構築
enum UserModuleBuilder {
    static func build(userId: Int) -> UIViewController {
        let service = UserService()
        let interactor = UserInteractor(service: service)
        let router = UserRouter()
        let presenter = UserPresenter(interactor: interactor, router: router)
        let viewController = UserViewController(presenter: presenter)
        presenter.setView(viewController)
        router.setViewController(viewController)
        return viewController
    }
}

Builder/AssemblerはVIPERの重要な要素であり、依存性注入を手動で実装します。コンストラクタを介した依存性注入(constructor injection)により、コンポーネントが依存関係なしに作成されることを防ぎます。現代のVIPERでは、BuilderはSwinject(DIコンテナ)を使用できますが、手動アセンブリはテストの透過性が高くなります。IT Sectrでは、複雑なロジックを持つモジュールには手動アセンブリのVIPERを適用しています。これにより、新規開発者のコード理解が容易になります。

Mapper(Formatter)はVIPERのオプションの6番目のコンポーネントです。MapperはEntity(DB/サーバーモデル)をViewModel(表示モデル)に変換します。Entityにはid、first_name、last_name、emailフィールドを持つUserDTOが含まれます。ViewModel — name(first_name + last_name)とemailを持つUserDisplayItem。MapperはPresenter内で実行されます。データマッピングが複雑な場合(複数のEntity→1つのViewModel)、Mapperはテスト用に別のクラスに抽出されます。

VIPERモジュール間の通信

VIPERモジュールは分離されており、互いについて知りません。モジュール間の通信はRouterを介して行われます。ユーザーがユーザー画面の「プロフィール」ボタンをクリックすると、Presenterはrouter.navigateToProfile(userId: 42)を呼び出します。RouterはProfileModuleBuilder.build(userId: 42)を介して新しいモジュールを作成し、navigationController.pushを介して開きます。データフロー:モジュールA → Router A → モジュールB Builder → モジュールBが作成され開かれます。

データの返却(例:選択画面で都市を選択→プロフィール編集画面に戻る)は、VIPERではデリゲートまたはクロージャを介して実装されます。モジュールBはdidSelectCity(_ city: City)メソッドを持つModuleBDelegateプロトコルを定義します。モジュールAはこのプロトコルを実装します。Router AはデリゲートをモジュールB Builderに渡します。都市が選択されると、モジュールBはdelegate?.didSelectCity(city)を呼び出します。これは標準的なiOSのプラクティスであり、UIKit開発者にはおなじみです。

シナリオメカニズム
前方ナビゲーションRouter → BuildernavigateToProfile(userId:)
データの返却DelegatedidSelectCity(_:)
システム通知NotificationCenterUserDidLogout
InteractorからのイベントPresenter → ViewWebSocketメッセージ

NotificationCenterは、複数のモジュールに同時に影響するシステムイベント(ログアウト、プラン変更、プッシュ通知)に使用されます。RouterまたはAppDelegateがNotificationを購読し、必要なモジュールを作成するか状態を更新します。VIPERはNotificationCenterを禁止していません。重要なのは、1対多のイベントにのみ使用し、1対1の通信にはデリゲートまたはクロージャを使用することです。

VIPERとMVVM・Clean Architectureの比較

VIPER vs MVVM — VIPERは画面ごとに2〜3倍のコードが必要ですが、絶対的なコンポーネントの分離を提供します。ViewModel + SwiftUIを使用したMVVMはよりシンプルで高速ですが、5人以上の開発者チームではスケーラビリティが低下します。VIPERは誰が何に責任を持つかを厳密に定義します。Interactor — ビジネスロジックのみ、Presenter — フォーマット、Router — ナビゲーション。MVVMでは、ViewModelがしばしば肥大化し、ナビゲーションとビジネスロジックを引き継ぎます。

VIPER vs Clean Architecture — VIPERはiOS UIKitに適応したClean Architectureの特定のケースです。Interactor = Use Case、Entity = Domain Model、Presenter = Presentation、Router = Controller(Robert Martinの用語)。Clean ArchitectureはInteractorとデータの間にGateway/Repository層を追加しますが、VIPERでは通常分離されません。現代のSwiftUIプロジェクトでは、ほとんどのチームがClean Architecture(The Composable Architecture)またはMVVMを選択し、VIPERはUIKitレガシーに残します。

VIPERを選ぶべき時 — 5人以上の開発者チーム、50画面以上のUIKitプロジェクト、80%以上のテスト要件、iOSのみ(VIPERは書き換えなしではAndroidに移植できません)。VIPERは予測可能な構造を提供します。新しい開発者は15分でモジュールを理解できます。ただし、ファイル数が多いため、開発速度はMVVMと比較して20〜30%低下します。IT Sectrでは、3人以上のチームによるエンタープライズUIKitプロジェクトにはVIPERを使用し、新しいSwiftUIプロジェクトにはClean Architectureを優先しています。

VIPERモジュールのテスト

VIPERはテスト用に設計されています — 各コンポーネントはプロトコルを介して分離してテストされます。Interactorはモックサービスでテストされ、fetchUserが正しいIDで呼び出され、結果がPresenterに渡されることを確認します。PresenterはモックのViewとInteractorオブジェクトでテストされます。Routerはモックナビゲーションでテストされ、navigateToProfileが正しいuserIdで呼び出され、正しいモジュールが作成されることを確認します。ViewはUIテスト(XCUITest)でテストされます。

swift
import XCTest

final class UserPresenterTests: XCTestCase {
    func testViewDidLoad_callsFetchUserAndUpdatesView() {
        // Given
        let view = MockUserView()
        let interactor = MockUserInteractor()
        let router = MockUserRouter()
        let presenter = UserPresenter(interactor: interactor, router: router)
        presenter.setView(view)
        let expectedUser = User(id: 42, name: "John", email: "john@test.com")
        interactor.result = .success(expectedUser)

        // When
        presenter.viewDidLoad()

        // Then
        XCTAssertEqual(interactor.capturedUserId, 42)
        XCTAssertEqual(view.displayedName, "John")
        XCTAssertTrue(view.didShowLoading)
    }
}

モックオブジェクトはVIPER用に手動で作成されるか(キャプチャプロパティを格納するクラス)、Cuckoo / Mockingbirdなどのライブラリを介して作成されます。手動モッククラスはよりシンプルで明確であり、特に新しい開発者のトレーニングに適しています。各モックはキャプチャされた値(capturedUserId、displayedName)と呼び出しフラグ(didShowLoading)を格納します。テストの終了時には、メソッドが呼び出されたかだけでなく、どのパラメータで呼び出されたかも確認されます。これにより、データフローの正確性に自信が持てます。

コードカバレッジはIT SectrのVIPERプロジェクトで、Interactorが85〜95%、Presenterが90〜95%、Routerが70〜80%、Viewが30〜50%(UIテスト経由)に達します。Viewはスナップショットテスト(SnapshotTesting、1.5Kスター)でテストされます。これはXCUITestより高速で、より多くのケースをカバーします。VIPERプロジェクトの全体的なカバレッジは通常70〜80%で、MVVMプロジェクト(50〜65%)より高いですが、テスト作成により多くの時間が必要です(開発時間の30〜40%対MVVMの20〜25%)。

よくある質問

1つのVIPERモジュールにいくつのファイルがありますか?

最低11ファイル:5つのプロトコル(ViewProtocol、InteractorProtocol、PresenterProtocol、RouterProtocol、Entity)、5つの実装(ViewController、Interactor、Presenter、Router、Entity)、およびBuilder/Assembler。Mapper(Formatter)を含めると12〜13ファイル。50画面のプロジェクトでは、VIPERモジュールだけで550〜650ファイルになります。MVVMでは画面ごとに3ファイル(ViewModel、View、Model)が必要で、50画面で150ファイルです。

AndroidでVIPERは使用できますか?

はい、理論的にはVIPERはAndroidに移植可能ですが、実際には使用されていません。GoogleはJetpackを使用したMVVMを推奨しています。VIPERはiOS UIKit用に作られ、ViewControllerはライフサイクルのためにテストが困難でした。Androidでは、Jetpack ViewModelがVIPERの分離なしでテスト問題を解決します。AndroidでのVIPER相当は、モジュール/機能分割を用いたClean Architectureです。

VIPERとClean Architectureの違いは?

VIPERはClean ArchitectureのiOS固有の実装です。InteractorはUse Case、EntityはDomain Model、PresenterはPresentation層に対応します。Clean ArchitectureはInteractorとデータの間にRepository/Gatewayを追加しますが、VIPERでは通常Interactor内部に実装されます。Clean ArchitectureはRouterを規定せず、ナビゲーションは実装に委ねられます。

SwiftUIプロジェクトにVIPERは必要ですか?

いいえ — SwiftUIはMVVM + Combine用に設計されています。SwiftUIのVIPERは冗長です。宣言型UIで画面ごとに5つのコンポーネントは、利点のないオーバーヘッドです。SwiftUIの場合は、MVVMまたはTCA(The Composable Architecture)を選択してください。VIPERはUIKitレガシーおよびiOS 12以下を最小バージョンとするプロジェクトに関連し続けています。

VIPERモジュール間でデータを渡すには?

Routerを介して行います。モジュールAはrouter.navigateToProfile(userId: id)を呼び出します。Router AはBuilderを介してモジュールBを作成し、userIdを渡します。コールバック通信はデリゲートを介して行います。モジュールBがModuleBDelegateプロトコルを定義し、モジュールAがそれを実装してRouterを介して渡します。システムイベント(ログアウト)はNotificationCenterを介して行います。

まとめ

  • VIPER — 厳格な分離を持つ5つのコンポーネント:View、Interactor、Presenter、Entity、Router
  • モジュール性 — 各画面は分離され、Builderがコンストラクタインジェクションで依存関係を組み立てる
  • Router — Presenterからナビゲーションを切り離し、iOSのナビゲーション問題を解決
  • Interactor — UIKitなしのクリーンなビジネスロジック、単体テストでテスト可能
  • コード量 — 画面ごとに11+ファイル、開発速度はMVVMより20〜30%低速
  • テスト — 70〜80%カバレッジ、InteractorとPresenterはモックオブジェクトでテスト
  • SwiftUI vs UIKit — UIKit(レガシー)にはVIPER、SwiftUIにはMVVM/TCA

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください