VIPER: temel kavramlar, View-Interactor-Presenter-Entity-Router deseni

Yazar: IT Sectr Yayınlanma: 2026-02-16 Okuma süresi: 9 dk

VIPER (View-Interactor-Presenter-Entity-Router) — Mutual Mobile şirketi tarafından iOS uygulamaları için geliştirilmiş modüler bir mimaridir. VIPER uygulamayı beş katmana ayırır: View görüntülemeden, Interactor iş mantığından, Presenter veri hazırlamadan, Entity veri modellerinden, Router modüller arası navigasyondan sorumludur. VIPER, mobil mimariler arasında tek sorumluluk ilkesinin en detaylı uygulamasıdır. Daha fazlası için objc.io'daki makaleye göz atın.

Önemli Noktalar

  • VIPER — beş bileşen: View, Interactor, Presenter, Entity, Router net sorumluluk sınırlarıyla
  • Modülerlik — her ekran (modül) izole edilmiştir, protokoller aracılığıyla iletişim
  • Router — navigasyonu Presenter'dan çıkarır, iOS navigasyon sorununu çözer
  • Interactor — iş mantığını içerir ve UIKit'e bağımlı değildir, birim testlerle test edilebilir
  • iOS-native — VIPER, SwiftUI'dan önce UIKit için oluşturuldu ve büyük iOS projeleri için standart olmaya devam ediyor

VIPER Nedir: Modüler Mimarinin Beş Bileşeni

VIPER (View-Interactor-Presenter-Entity-Router) — 2013–2014 yıllarında Mutual Mobile'da büyük iOS projeleri için geliştirilmiş bir mimari desendir. Uygulamanın her ekranı, kesin olarak tanımlanmış sorumluluklara sahip beş bileşenden oluşan ayrı bir modüldür. VIPER, mobil geliştirmede Tek Sorumluluk İlkesinin (Single Responsibility Principle) en katı uygulamasıdır: hiçbir bileşen, başka bir bileşenin yapabileceğini yapmaz.

View, yalnızca Presenter tarafından iletilen verileri görüntülemekten sorumlu pasif bir bileşendir. View iş mantığı içermez, navigasyonu yönetmez, ağ istekleri yapmaz. iOS'ta — ViewProtocol ile UIViewController. Interactor, Entity ve hizmetlerle (ağ, DB, GPS) çalışan iş mantığı katmanıdır. Interactor UIKit'i içe aktarmaz. Presenter, View ve Interactor arasındaki arabulucudur: Interactor'dan veri alır, görüntüleme için biçimlendirir, View'a iletir. Presenter da UIKit'i içe aktarmaz. Entity — veri modelleri (struct, class). Router — navigasyonu yönetir: modüller oluşturur, ekranları açar, modüller arasında veri iletir.

BileşenSorumlulukBağımlılıklar
ViewGörüntüleme, animasyonlar, jestlerUIKit (yalnızca View)
Interactorİş mantığı, ağ, DBEntity, hizmetler
PresenterVeri biçimlendirme, View komutlarıViewProtocol, Interactor
EntityVeri modelleriHiçbiri
RouterNavigasyon, modül oluşturmaUIViewController (geçişler için)

Bileşenler arasındaki ilişkiler protokollerle tanımlanır. ViewProtocol görüntüleme yöntemlerini, InteractorProtocol iş mantığı yöntemlerini, PresenterProtocol olay işleme yöntemlerini, RouterProtocol navigasyon yöntemlerini tanımlar. Her bileşen diğeriyle yalnızca bir protokol aracılığıyla iletişim kurar, bu da uygulamaların kolayca değiştirilmesine ve izole test edilmesine olanak tanır. Ortalama olarak, bir ekran için VIPER modülü 5 protokol + 5 sınıf + 1 Builder/Assembler = ekran başına 11 dosya içerir.

Swift'te VIPER: Modül, Router ve Presenter

VIPER modülü oluşturma Builder (veya Assembler) içinde yapılır; bu, beş bileşenin tümünü oluşturur ve protokoller aracılığıyla bağlar. Builder, bileşenlerin birbirlerinin somut türlerini bildiği tek yerdir. Montajdan sonra View, görüntüleme için dışarıya döndürülür, zincirin geri kalanı temizdir ve izole olarak test edilir.

swift
// Protokol — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

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

// Protokol — Router
protocol UserRouterProtocol {
    func navigateToProfile(userId: Int)
}

// Interactor — iş mantığı
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 — veri hazırlama
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):
                // hata işleme
            }
        }
    }
}

// Router — navigasyon
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 — modül montajı
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, Bağımlılık Enjeksiyonunu manuel olarak uygulayan VIPER'ın önemli bir öğesidir. Yapıcı aracılığıyla bağımlılık enjeksiyonu (constructor injection), bir bileşenin bağımlılıkları olmadan oluşturulamamasını garanti eder. Modern VIPER'da Builder, Swinject (DI kapsayıcısı) kullanabilir, ancak manuel montaj test için daha şeffaf kalır. IT Sectr'da, karmaşık mantığa sahip modüller için manuel montajlı VIPER uyguluyoruz — bu, yeni geliştiricilerin kodu okumasını kolaylaştırır.

Mapper (Formatter), VIPER'ın isteğe bağlı altıncı bileşenidir. Mapper, Entity'yi (DB/sunucu modelleri) ViewModel'e (görüntüleme modelleri) dönüştürür. Entity, id, first_name, last_name, email alanlarına sahip UserDTO içerir. ViewModel — name (first_name + last_name) ve email ile UserDisplayItem. Mapper, Presenter içinde yürütülür. Veri eşlemesi karmaşıksa (birden çok Entity → bir ViewModel), Mapper test için ayrı bir sınıfa çıkarılır.

VIPER Modülleri Arasında İletişim

VIPER modülleri izole edilmiştir ve birbirlerini bilmezler. Modüller arası iletişim Router aracılığıyla gerçekleşir. Kullanıcı, kullanıcı ekranındaki "Profil" düğmesine tıkladığında, Presenter router.navigateToProfile(userId: 42) çağrısını yapar. Router, ProfileModuleBuilder.build(userId: 42) aracılığıyla yeni bir modül oluşturur ve navigationController.push ile açar. Veri akışı: Modül A → Router A → Modül B Builder → Modül B oluşturulur ve açılır.

Verileri geri iletme (örneğin, seçim ekranında bir şehir seçme → profil düzenleme ekranına geri dönme) VIPER'da delegeler veya closure'lar aracılığıyla uygulanır. Modül B, didSelectCity(_ city: City) yöntemiyle ModuleBDelegate protokolünü tanımlar. Modül A bu protokolü uygular. Router A, delegcyi Modül B Builder'a iletir. Bir şehir seçildiğinde, Modül B delegate?.didSelectCity(city) çağrısını yapar. Bu, her UIKit geliştiricisinin aşina olduğu standart bir iOS uygulamasıdır.

SenaryoMekanizmaÖrnek
İleri navigasyonRouter → BuildernavigateToProfile(userId:)
Veri geri iletmeDelegatedidSelectCity(_:)
Sistem bildirimiNotificationCenterUserDidLogout
Interactor'dan olayPresenter → ViewWebSocket mesajı

NotificationCenter, aynı anda birden çok modülü etkileyen sistem olayları (oturum kapatma, plan değişikliği, push bildirimleri) için kullanılır. Router veya AppDelegate, Notification'a abone olur, gerekli modülü oluşturur veya durumu günceller. VIPER, NotificationCenter'ı yasaklamaz — önemli olan, yalnızca 1-çok olayları için kullanılması, 1-1 iletişim için ise delegeler veya closure'lar kullanılmasıdır.

VIPER'ın MVVM ve Clean Architecture ile Karşılaştırılması

VIPER vs MVVM — VIPER, ekran başına 2–3 kat daha fazla kod gerektirir ancak mutlak bileşen izolasyonu sağlar. ViewModel + SwiftUI ile MVVM daha basit ve hızlıdır ancak 5+ geliştiricili ekipler için daha az ölçeklenebilir. VIPER, kimin neden sorumlu olduğunu katı bir şekilde tanımlar: Interactor — yalnızca iş mantığı, Presenter — biçimlendirme, Router — navigasyon. MVVM'de ViewModel genellikle büyür, navigasyonu ve iş mantığını üstlenir.

VIPER vs Clean Architecture — VIPER, iOS UIKit için uyarlanmış Clean Architecture'ın belirli bir durumudur. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller (Robert Martin'in terimleriyle). Clean Architecture, Interactor ve veri arasına bir Gateway/Repository katmanı ekler; bu, VIPER'da genellikle ayrılmaz. Modern SwiftUI projeleri için çoğu ekip Clean Architecture (The Composable Architecture) veya MVVM'yi seçer ve VIPER'ı UIKit mirasına bırakır.

VIPER ne zaman seçilmeli — 5+ geliştiricili ekipler, 50+ ekranlı UIKit projeleri, %80'in üzerinde test gereksinimleri, yalnızca iOS (VIPER, yeniden yazılmadan Android'e taşınamaz). VIPER, öngörülebilir bir yapı sağlar: yeni bir geliştirici 15 dakikada bir modülü anlar. Ancak, daha fazla dosya nedeniyle geliştirme hızı MVVM'ye kıyasla %20–30 daha düşüktür. IT Sectr'da, 3+ kişilik ekiplerle kurumsal UIKit projeleri için VIPER kullanıyoruz ve yeni SwiftUI projeleri için Clean Architecture'ı tercih ediyoruz.

VIPER Modüllerinin Test Edilmesi

VIPER test için tasarlanmıştır — her bileşen protokoller aracılığıyla izole olarak test edilir. Interactor, mock hizmetlerle test edilir: fetchUser'ın doğru ID ile çağrıldığı ve sonucun Presenter'a iletildiği kontrol edilir. Presenter, mock View ve Interactor nesneleriyle test edilir. Router, mock navigasyonla test edilir: navigateToProfile'ın doğru userId ile çağrıldığı ve doğru modülün oluşturulduğu kontrol edilir. View, UI testleriyle (XCUITest) test edilir.

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 nesneler VIPER için manuel olarak (depolanmış yakalama özelliklerine sahip sınıf) veya Cuckoo / Mockingbird gibi kütüphaneler aracılığıyla oluşturulur. Manuel mock sınıfları, özellikle yeni geliştiricileri eğitmek için daha basit ve anlaşılırdır. Her mock, yakalanan değerleri (capturedUserId, displayedName) ve çağrı bayraklarını (didShowLoading) depolar. Testin sonunda, yalnızca yöntemin çağrılıp çağrılmadığı değil, aynı zamanda hangi parametrelerle çağrıldığı da kontrol edilir — bu, veri akışının doğruluğuna güven verir.

Kod kapsamı IT Sectr VIPER projelerinde Interactor için %85–95, Presenter için %90–95, Router için %70–80, View için %30–50 (UI testleri aracılığıyla) seviyelerine ulaşır. View, anlık görüntü testleriyle (SnapshotTesting, 1,5K yıldız) test edilir — bu, XCUITest'ten daha hızlıdır ve daha fazla durumu kapsar. Bir VIPER projesinin genel kapsamı genellikle %70–80'dir, bu bir MVVM projesinden (%50–65) daha yüksektir, ancak test yazmak için daha fazla zaman gerektirir (geliştirme süresinin %30–40'ına karşı MVVM'de %20–25).

Sıkça Sorulan Sorular

Bir VIPER modülünde kaç dosya var?

En az 11 dosya: 5 protokol (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 uygulama (ViewController, Interactor, Presenter, Router, Entity) ve Builder/Assembler. Mapper (Formatter) ile — 12–13. 50 ekranlı bir proje için, yalnızca VIPER modüllerinden 550–650 dosya olur. MVVM, ekran başına 3 dosya gerektirir (ViewModel, View, Model) — 50 ekran için 150 dosya.

VIPER Android'de kullanılabilir mi?

Evet, teorik olarak VIPER Android'e taşınabilir, ancak pratikte kullanılmaz — Google, Jetpack ile MVVM'yi önerir. VIPER, ViewController'ın yaşam döngüsü nedeniyle test edilmesi zor olan iOS UIKit için oluşturuldu. Android'de, Jetpack ViewModel, VIPER izolasyonu olmadan test sorununu çözer. Android'de VIPER'ın eşdeğeri, modül/özellik ayrımına sahip Clean Architecture'dır.

VIPER ve Clean Architecture arasındaki fark nedir?

VIPER, Clean Architecture'ın iOS'a özgü bir uygulamasıdır. Interactor, Use Case'e, Entity — Domain Model'e, Presenter — Presentation katmanına karşılık gelir. Clean Architecture, Interactor ve veri arasına Repository/Gateway ekler; VIPER'da bunlar genellikle Interactor içinde uygulanır. Clean Architecture, Router'ı belirlemez — navigasyon uygulamaya bırakılır.

SwiftUI projeleri için VIPER gerekli mi?

Hayır — SwiftUI, MVVM + Combine için tasarlanmıştır. SwiftUI'da VIPER gereksizdir: bildirimsel UI ile ekran başına beş bileşen, faydasız bir yüktür. SwiftUI için MVVM veya TCA (The Composable Architecture) seçin. VIPER, UIKit mirası ve iOS 12 ve altının minimum sürüm olduğu projeler için geçerliliğini korur.

VIPER modülleri arasında veri nasıl iletilir?

Router aracılığıyla. Modül A, router.navigateToProfile(userId: id) çağrısını yapar. Router A, Builder aracılığıyla modül B'yi oluşturur, userId'yi iletir. Geri bildirim iletişimi — bir delege aracılığıyla: modül B, ModuleBDelegate protokolünü tanımlar, modül A bunu uygular ve Router aracılığıyla iletir. Sistem olayları (oturum kapatma) — NotificationCenter aracılığıyla.

Özet

  • VIPER — katı ayrıma sahip beş bileşen: View, Interactor, Presenter, Entity, Router
  • Modülerlik — her ekran izole edilmiştir, Builder bağımlılıkları constructor injection ile birleştirir
  • Router — navigasyonu Presenter'dan çıkarır, iOS'ta navigasyon sorununu çözer
  • Interactor — UIKit olmadan temiz iş mantığı, birim testlerle test edilebilir
  • Kod hacmi — ekran başına 11+ dosya, geliştirme MVVM'den %20–30 daha yavaş
  • Test — %70–80 kapsam, Interactor ve Presenter mock nesnelerle test edilir
  • SwiftUI vs UIKit — UIKit (miras) için VIPER, SwiftUI için MVVM/TCA

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun