VIPER: مفاهیم کلیدی، الگوی View-Interactor-Presenter-Entity-Router

نویسنده: IT Sectr منتشر شده: 2026-02-16 زمان مطالعه: 9 دقیقه

VIPER (View-Interactor-Presenter-Entity-Router) — معماری ماژولار توسعه‌یافته در شرکت Mutual Mobile برای برنامه‌های iOS. VIPER برنامه را به پنج لایه تقسیم می‌کند: View مسئول نمایش، Interactor مسئول منطق کسب‌وکار، Presenter مسئول آماده‌سازی داده‌ها، Entity مسئول مدل‌های داده، Router مسئول ناوبری بین ماژول‌ها. VIPER دقیق‌ترین پیاده‌سازی اصل مسئولیت واحد در میان معماری‌های موبایل است. بیشتر — در مقاله در objc.io.

نکات اصلی

  • VIPER — پنج مؤلفه: View، Interactor، Presenter، Entity، Router با مرزهای مسئولیت مشخص
  • ماژولار بودن — هر صفحه (ماژول) ایزوله است، ارتباط از طریق پروتکل‌ها
  • Router — ناوبری را از Presenter خارج می‌کند و مشکل ناوبری iOS را حل می‌کند
  • Interactor — حاوی منطق کسب‌وکار است و به UIKit وابسته نیست، با تست‌های واحد آزمایش می‌شود
  • iOS-native — VIPER برای UIKit قبل از ظهور SwiftUI ایجاد شد و استاندارد پروژه‌های بزرگ iOS باقی می‌ماند

VIPER چیست: پنج مؤلفه معماری ماژولار

VIPER (View-Interactor-Presenter-Entity-Router) — الگوی معماری توسعه‌یافته در سال‌های ۲۰۱۳–۲۰۱۴ در شرکت Mutual Mobile برای پروژه‌های بزرگ iOS. هر صفحه برنامه یک ماژول مجزا از پنج مؤلفه با وظایف کاملاً مشخص است. VIPER سخت‌گیرانه‌ترین پیاده‌سازی اصل مسئولیت واحد (Single Responsibility Principle) در توسعه موبایل است: هیچ مؤلفه‌ای کاری را که مؤلفه دیگر می‌تواند انجام دهد، انجام نمی‌دهد.

View — مؤلفه منفعل که فقط مسئول نمایش داده‌های ارسال‌شده توسط Presenter است. View حاوی منطق کسب‌وکار نیست، ناوبری را مدیریت نمی‌کند، درخواست‌های شبکه را انجام نمی‌دهد. در iOS — UIViewController با پروتکل ViewProtocol. Interactor — لایه منطق کسب‌وکار که با Entity و سرویس‌ها (شبکه، دیتابیس، GPS) کار می‌کند. Interactor UIKit را import نمی‌کند. Presenter — واسط بین View و Interactor: داده‌ها را از Interactor دریافت می‌کند، برای نمایش قالب‌بندی می‌کند و به View منتقل می‌کند. Presenter نیز UIKit را import نمی‌کند. Entity — مدل‌های داده (struct، class). Router — ناوبری را مدیریت می‌کند: ماژول‌ها را ایجاد می‌کند، صفحه‌ها را باز می‌کند، داده‌ها را بین ماژول‌ها منتقل می‌کند.

مؤلفهمسئولیتوابستگی‌ها
Viewنمایش، انیمیشن‌ها، ژست‌هاUIKit (فقط View)
Interactorمنطق کسب‌وکار، شبکه، دیتابیسEntity، سرویس‌ها
Presenterقالب‌بندی داده‌ها، دستورات ViewViewProtocol، Interactor
Entityمدل‌های دادهندارد
Routerناوبری، ایجاد ماژول‌هاUIViewController (برای انتقال)

ارتباطات بین مؤلفه‌ها توسط پروتکل‌ها توصیف می‌شوند. ViewProtocol متدهای نمایش، InteractorProtocol — متدهای منطق کسب‌وکار، PresenterProtocol — متدهای پردازش رویداد، RouterProtocol — متدهای ناوبری را تعریف می‌کند. هر مؤلفه فقط از طریق پروتکل با دیگری ارتباط برقرار می‌کند که امکان تعویض آسان پیاده‌سازی‌ها و تست ایزوله را فراهم می‌کند. به طور متوسط یک ماژول VIPER از یک صفحه شامل ۵ پروتکل + ۵ کلاس + ۱ Builder/Assembler = ۱۱ فایل در هر صفحه است.

VIPER در Swift: ماژول، Router و Presenter

ساخت ماژول VIPER در Builder (یا Assembler) انجام می‌شود که هر پنج مؤلفه را ایجاد کرده و از طریق پروتکل‌ها به هم متصل می‌کند. 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 که تزریق وابستگی (Dependency Injection) را به صورت دستی پیاده‌سازی می‌کند. تزریق وابستگی از طریق سازنده (constructor injection) تضمین می‌کند که مؤلفه بدون وابستگی‌های خود نمی‌تواند ایجاد شود. در VIPER مدرن، Builder ممکن است از Swinject (کانتینر DI) استفاده کند، اما ساخت دستی برای تست شفاف‌تر باقی می‌ماند. در IT Sectr ما از VIPER با ساخت دستی برای ماژول‌های با منطق پیچیده استفاده می‌کنیم — این کار خواندن کد را برای توسعه‌دهندگان جدید آسان‌تر می‌کند.

Mapper (Formatter) — مؤلفه ششم اختیاری VIPER. Mapper Entity (مدل‌های دیتابیس/سرور) را به ViewModel (مدل‌های نمایش) تبدیل می‌کند. Entity شامل UserDTO با فیلدهای id، first_name، last_name، email است. ViewModel — UserDisplayItem با name (first_name + last_name) و email. Mapper در Presenter اجرا می‌شود. اگر نگاشت داده پیچیده باشد (چندین Entity → یک ViewModel)، Mapper برای تست به یک کلاس جداگانه منتقل می‌شود.

ارتباط بین ماژول‌های VIPER

ماژول‌های VIPER ایزوله هستند و از یکدیگر اطلاعی ندارند. ارتباط بین ماژول‌ها از طریق Router انجام می‌شود. هنگامی که کاربر دکمه «پروفایل» را در صفحه کاربر کلیک می‌کند، Presenter router.navigateToProfile(userId: 42) را فراخوانی می‌کند. Router یک ماژول جدید از طریق ProfileModuleBuilder.build(userId: 42) ایجاد کرده و آن را از طریق navigationController.push باز می‌کند. جریان داده: ماژول A → Router A → ماژول B Builder → ماژول B ایجاد و باز می‌شود.

انتقال داده به عقب (مثلاً شهری در صفحه انتخاب انتخاب شد → به صفحه ویرایش پروفایل برگردانده شد) در VIPER از طریق دلیگیت‌ها یا بلاک‌ها (closures) پیاده‌سازی می‌شود. ماژول B پروتکل ModuleBDelegate را با متد didSelectCity(_ city: City) تعریف می‌کند. ماژول A این پروتکل را پیاده‌سازی می‌کند. Router A دلیگیت را به Module B Builder منتقل می‌کند. هنگام انتخاب شهر، ماژول B delegate?.didSelectCity(city) را فراخوانی می‌کند. این یک روش استاندارد iOS است که برای هر توسعه‌دهنده UIKit آشناست.

سناریومکانیزممثال
انتقال به جلوRouter → BuildernavigateToProfile(userId:)
انتقال داده به عقبDelegatedidSelectCity(_:)
اعلان سیستمیNotificationCenterUserDidLogout
رویداد از InteractorPresenter → ViewWebSocket message

NotificationCenter برای رویدادهای سیستمی (خروج از حساب، تغییر تعرفه، اعلان‌های پوش) که همزمان چند ماژول را تحت تأثیر قرار می‌دهند استفاده می‌شود. Router یا AppDelegate در Notification مشترک شده، ماژول مورد نیاز را ایجاد یا وضعیت را به‌روزرسانی می‌کند. VIPER NotificationCenter را منع نمی‌کند — مهم این است که فقط برای رویدادهای یک-به-چند استفاده شود و برای ارتباط یک-به-یک از دلیگیت‌ها یا بلاک‌ها استفاده گردد.

مقایسه VIPER با MVVM و Clean Architecture

VIPER vs MVVM — VIPER به ۲–۳ برابر کد بیشتر در هر صفحه نیاز دارد، اما ایزولاسیون مطلق مؤلفه‌ها را فراهم می‌کند. MVVM با ViewModel + SwiftUI ساده‌تر و سریع‌تر است، اما در تیم‌های ۵+ توسعه‌دهنده مقیاس‌پذیری کمتری دارد. VIPER به طور سخت مشخص می‌کند چه کسی مسئول چه چیزی است: Interactor — فقط منطق کسب‌وکار، Presenter — قالب‌بندی، Router — ناوبری. در MVVM، ViewModel اغلب بزرگ شده و ناوبری و منطق کسب‌وکار را به خود می‌گیرد.

VIPER vs Clean Architecture — VIPER یک مورد خاص از Clean Architecture است که برای iOS UIKit تطبیق داده شده است. Interactor = Use Case، Entity = Domain Model، Presenter = Presentation، Router = Controller در اصطلاحات رابرت مارتین. Clean Architecture لایه Gateway/Repository را بین Interactor و داده‌ها اضافه می‌کند که در VIPER معمولاً جدا نمی‌شود. برای پروژه‌های مدرن در SwiftUI، اکثر تیم‌ها Clean Architecture (The Composable Architecture) یا MVVM را انتخاب کرده و VIPER را برای کدهای قدیمی UIKit نگه می‌دارند.

چه زمانی VIPER را انتخاب کنیم — تیم‌های از ۵ توسعه‌دهنده، پروژه UIKit از ۵۰ صفحه به بالا، الزامات تست بالای ۸۰٪، فقط iOS (VIPER بدون بازنویسی به Android منتقل نمی‌شود). VIPER ساختاری قابل پیش‌بینی ارائه می‌دهد: یک توسعه‌دهنده جدید ماژول را در ۱۵ دقیقه درک می‌کند. با این حال سرعت توسعه به دلیل تعداد بیشتر فایل‌ها ۲۰–۳۰٪ کمتر از MVVM است. در IT Sectr ما از VIPER برای پروژه‌های سازمانی UIKit با تیم‌های از ۳ نفر استفاده می‌کنیم و Clean Architecture را برای پروژه‌های جدید در SwiftUI ترجیح می‌دهیم.

تست ماژول‌های VIPER

VIPER برای تست طراحی شده است — هر مؤلفه به صورت ایزوله از طریق پروتکل‌ها تست می‌شود. Interactor با سرویس‌های mock تست می‌شود: بررسی می‌شود که fetchUser با ID صحیح فراخوانی شده و نتیجه به Presenter منتقل شده است. Presenter با اشیاء mock View و Interactor تست می‌شود. Router با ناوبری mock تست می‌شود: بررسی می‌شود که 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)
    }
}

اشیاء mock برای VIPER به صورت دستی (کلاس با ویژگی‌های ذخیره‌سازی capture) یا از طریق کتابخانه‌های Cuckoo / Mockingbird ایجاد می‌شوند. کلاس‌های mock دستی ساده‌تر و قابل‌فهم‌تر هستند، به ویژه برای آموزش توسعه‌دهندگان جدید. هر mock مقادیر ضبط شده (capturedUserId، displayedName) و پرچم‌های فراخوانی (didShowLoading) را ذخیره می‌کند. در پایان تست نه تنها اینکه متد فراخوانی شده، بلکه با چه پارامترهایی فراخوانی شده نیز بررسی می‌شود — این اطمینان از صحت جریان داده را فراهم می‌کند.

پوشش کد در پروژه‌های VIPER IT Sectr به ۸۵–۹۵٪ برای Interactor، ۹۰–۹۵٪ برای Presenter، ۷۰–۸۰٪ برای Router، ۳۰–۵۰٪ برای View (از طریق تست‌های UI) می‌رسد. View با تست‌های اسکرین‌شات (SnapshotTesting، ۱٫۵K ستاره) تست می‌شود — این سریع‌تر از XCUITest است و موارد بیشتری را پوشش می‌دهد. پوشش کلی پروژه VIPER معمولاً ۷۰–۸۰٪ است که بالاتر از پروژه MVVM (۵۰–۶۵٪) است، اما به زمان بیشتری برای نوشتن تست نیاز دارد (۳۰–۴۰٪ زمان توسعه در مقابل ۲۰–۲۵٪ در MVVM).

سؤالات متداول

یک ماژول VIPER چند فایل دارد؟

حداقل ۱۱ فایل: ۵ پروتکل (ViewProtocol، InteractorProtocol، PresenterProtocol، RouterProtocol، Entity)، ۵ پیاده‌سازی (ViewController، Interactor، Presenter، Router، Entity) و Builder/Assembler. با Mapper (Formatter) — ۱۲–۱۳. برای یک پروژه با ۵۰ صفحه این ۵۵۰–۶۵۰ فایل فقط برای ماژول‌های VIPER است. MVVM به ۳ فایل در هر صفحه نیاز دارد (ViewModel، View، Model) — ۱۵۰ فایل برای ۵۰ صفحه.

آیا می‌توان از VIPER در Android استفاده کرد؟

بله، از نظر تئوری VIPER به Android قابل انتقال است، اما در عمل استفاده نمی‌شود — Google MVVM با Jetpack را توصیه می‌کند. VIPER برای iOS UIKit ایجاد شده است، جایی که ViewController به دلیل چرخه حیات به سختی تست می‌شود. در Android، Jetpack ViewModel مشکل تست را بدون ایزولاسیون VIPER حل می‌کند. معادل Android VIPER — Clean Architecture با تقسیم به module/feature است.

تفاوت بین VIPER و Clean Architecture چیست؟

VIPER یک پیاده‌سازی خاص iOS از Clean Architecture است. Interactor معادل Use Case، Entity معادل Domain Model، Presenter معادل لایه Presentation است. Clean Architecture لایه Repository/Gateway را بین Interactor و داده‌ها اضافه می‌کند که در VIPER معمولاً در داخل Interactor پیاده‌سازی می‌شود. Clean Architecture Router را تجویز نمی‌کند — ناوبری به تصمیم پیاده‌سازی واگذار می‌شود.

آیا VIPER برای پروژه‌های SwiftUI لازم است؟

خیر — SwiftUI برای MVVM + Combine طراحی شده است. VIPER در SwiftUI بیش از حد است: پنج مؤلفه برای یک صفحه با UI اعلانی سرباری بدون فایده است. برای SwiftUI MVVM یا TCA (The Composable Architecture) را انتخاب کنید. VIPER برای کدهای قدیمی UIKit و پروژه‌هایی که iOS 12 و پایین‌تر حداقل نسخه هستند، همچنان مرتبط است.

چگونه داده‌ها را بین ماژول‌های VIPER منتقل کنیم؟

از طریق Router. ماژول A router.navigateToProfile(userId: id) را فراخوانی می‌کند. Router A ماژول B را از طریق Builder ایجاد کرده و userId را منتقل می‌کند. انتقال برگشتی — از طریق دلیگیت: ماژول B پروتکل ModuleBDelegate را تعریف می‌کند، ماژول A آن را پیاده‌سازی کرده و از طریق Router منتقل می‌کند. رویدادهای سیستمی (خروج) — از طریق NotificationCenter.

خلاصه

  • VIPER — پنج مؤلفه با تقسیم سخت: View، Interactor، Presenter، Entity، Router
  • ماژولار بودن — هر صفحه ایزوله است، Builder وابستگی‌ها را از طریق constructor injection جمع می‌کند
  • Router — ناوبری را از Presenter خارج می‌کند و مشکل ناوبری در iOS را حل می‌کند
  • Interactor — منطق کسب‌وکار خالص بدون UIKit، با تست‌های واحد آزمایش می‌شود
  • حجم کد — ۱۱+ فایل در هر صفحه، توسعه ۲۰–۳۰٪ کندتر از MVVM
  • تست — پوشش ۷۰–۸۰٪، Interactor و Presenter از طریق اشیاء mock تست می‌شوند
  • SwiftUI vs UIKit — VIPER برای UIKit (کد قدیمی)، MVVM/TCA برای SwiftUI

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید