Настройка архитектуры 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 без симулятора. Это то, ради чего стоит платить стоимость бойлерплейта.
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 — чище и без бойлерплейта. Для Flutter — BLoC с Clean Architecture ближе к VIPER по идеологии, но без UIKit-зависимостей.
Выбор в пользу VIPER обоснован, если:
- Существующий проект уже на VIPER (рефакторить нет смысла)
- Команда 5+ iOS-разработчиков, активный параллельный код
- Высокие требования к тестируемости каждого слоя
Что настраиваем
Проектируем протоколы → создаём Xcode-шаблон или настраиваем Generamba → реализуем базовый VIPER-модуль с тестами как образец → документируем правила для команды → при необходимости мигрируем приоритетные MVC/MVP-экраны.
Работа занимает 3–5 дней для нового проекта, включая генератор и документацию. Стоимость рассчитывается после анализа количества модулей и состава команды.







