Настройка архітектури VIPER для iOS-застосунку
VIPER — найдеталізованіша з архітектур для iOS. View, Interactor, Presenter, Entity, Router — п'ять компонентів на один екран. У невеликих командах на це дивляться зі скепсисом: багато файлів, багато протоколів, багато коду. У командах 5+ осіб, що працюють над одним UIKit-застосунком, VIPER стає конкурентною перевагою: ізольовані модулі не конфліктують у git, Interactor тестується без UI, Router інкапсулює навігацію.
Анатомія VIPER-модуля
Один екран = один VIPER-модуль. Для екрана профіля:
ProfileModule/
ProfileView.swift // UIViewController, реалізує ProfileViewProtocol
ProfilePresenter.swift // логіка представлення, реалізує ProfilePresenterProtocol
ProfileInteractor.swift // бізнес-логіка та робота з даними
ProfileRouter.swift // навігація, реалізує ProfileRouterProtocol
ProfileAssembly.swift // фабрика, збирає модуль та ін'єктує залежності
Protocols/
ProfileProtocols.swift // всі протоколи модуля в одному файлі
Протоколи — основа VIPER:
// View ← Presenter
protocol ProfileViewProtocol: AnyObject {
func displayUser(_ viewModel: ProfileViewModel)
func displayError(_ message: String)
func setLoading(_ loading: Bool)
}
// Presenter ← View
protocol ProfileViewToPresenterProtocol: AnyObject {
func viewDidLoad()
func editButtonTapped()
func settingsTapped()
}
// Presenter ← Interactor
protocol ProfileInteractorOutputProtocol: AnyObject {
func didFetchUser(_ user: User)
func didFailFetchUser(_ error: Error)
}
// Interactor ← Presenter
protocol ProfileInteractorInputProtocol: AnyObject {
func fetchUser()
}
// Router ← Presenter
protocol ProfileRouterProtocol: AnyObject {
func navigateToEditProfile(user: User)
func navigateToSettings()
}
Це багато коду. Саме тому для VIPER створюють Xcode-шаблони або генератори (Generamba, Vipergen) — один шаблон, команда generate profile створює всі 7 файлів з базовим кодом.
Interactor — серце бізнес-логіки
Interactor — єдине місце, де живе бізнес-логіка. Він не знає про UIKit, не знає про конкретне сховище:
final class ProfileInteractor {
weak var presenter: ProfileInteractorOutputProtocol?
private let userRepository: UserRepositoryProtocol
init(userRepository: UserRepositoryProtocol) {
self.userRepository = userRepository
}
}
extension ProfileInteractor: ProfileInteractorInputProtocol {
func fetchUser() {
Task {
do {
let user = try await userRepository.fetchCurrentUser()
await MainActor.run {
presenter?.didFetchUser(user)
}
} catch {
await MainActor.run {
presenter?.didFailFetchUser(error)
}
}
}
}
}
Тестування Interactor: підміняємо UserRepositoryProtocol моком — чистий Swift, XCTest без симулятора. Це те, ради чого варто платити вартість boilerplate.
Presenter: трансформація даних
Presenter отримує доменні Entity від Interactor та створює ViewModel для View:
extension ProfilePresenter: ProfileInteractorOutputProtocol {
func didFetchUser(_ user: User) {
let viewModel = ProfileViewModel(
displayName: "\(user.firstName) \(user.lastName)",
avatarURL: user.avatarURL,
memberSince: DateFormatter.mediumStyle.string(from: user.createdAt),
isVerified: user.verificationStatus == .verified
)
view?.displayUser(viewModel)
view?.setLoading(false)
}
}
ProfileViewModel — структура з UI-даними, не доменна User. View отримує готові рядки, не займається форматуванням.
Assembly: збірка модуля
Вся ініціалізація та DI — в Assembly:
enum ProfileAssembly {
static func build(coordinator: AppCoordinator) -> UIViewController {
let interactor = ProfileInteractor(
userRepository: DI.resolve(UserRepositoryProtocol.self)
)
let router = ProfileRouter(coordinator: coordinator)
let presenter = ProfilePresenter(interactor: interactor, router: router)
let view = ProfileViewController()
view.presenter = presenter
presenter.view = view
interactor.presenter = presenter
return view
}
}
Вся граф залежностей модуля видна в одному файлі. При додаванні нової залежності — тільки Assembly змінюється, решта компоненти ізольовані.
Генератори коду
Писати VIPER вручну — дуже дорого по часу. Інструменти:
-
Generamba — Ruby gem, шаблони через YAML, інтеграція в Xcode з командою
generamba gen ProfileModule viper - XcodeGen з кастомними шаблонами
- Swift Package з Makefile — власний генератор на основі Stencil-шаблонів
На проектах з 30+ модулями без генератора VIPER перетворюється в біль.
VIPER vs Clean Architecture + MVVM
VIPER органічен для UIKit + Objective-C legacy або великих команд з усталеними процесами. Для нових проектів на SwiftUI Clean Architecture + MVVM + @Observable — чистіше та без boilerplate. Для Flutter — BLoC з Clean Architecture ближе до VIPER за ідеологією, але без UIKit-залежностей.
Вибір на користь VIPER обґрунтований, якщо:
- Існуючий проект уже на VIPER (рефакторити нема сенсу)
- Команда 5+ iOS-розробників, активний паралельний код
- Високі вимоги до тестованості кожного шару
Що настраюємо
Проектуємо протоколи → створюємо Xcode-шаблон або настраюємо Generamba → реалізуємо базовий VIPER-модуль з тестами як образец → документуємо правила для команди → при необхідності мігруємо пріоритетні MVC/MVP-екрани.
Робота займає 3–5 днів для нового проекту, включаючи генератор та документацію. Вартість розраховується після аналізу кількості модулів та складу команди.







