Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Роль Router (Маршрутизатора) в iOS-архитектуре
Router (также часто называемый Coordinator или Wireframe) — это архитектурный компонент, который я использую для абстрагирования навигационной логики и управления потоком экранов в приложении. Его основная цель — отделить ответственность за переходы между модулями (ViewController'ами, экранами) от самих ViewController'ов, делая код более модульным, тестируемым и поддерживаемым.
Ключевые задачи Router
- Инкапсуляция навигации: ViewController больше не должен знать, как и куда переходить. Он просто сообщает Router'у о пользовательском действии (например, "пользователь нажал кнопку 'Детали'"), а Router решает, какой следующий экран показать и как (push, present, pop и т.д.).
- Управление зависимостями (Dependency Injection): Router часто выступает в роли сборщика (Assembler) для следующего экрана. Он создает все необходимые компоненты (View, Presenter/ViewModel, Interactor) и инжектит зависимости.
- Контроль потока приложения: Сложные сценарии, такие как авторизация, онбординг или глубокие ссылки (deeplinks), централизованно управляются через Router/Coordinator. Это позволяет легко изменять логику переходов в одном месте.
- Снижение связанности (Coupling): Модули становятся независимыми. Они не импортируют и не ссылаются друг на друга напрямую, общаясь через абстракции (протоколы/замыкания). Это прямой принцип SOLID (принцип разделения интерфейса и инверсии зависимостей).
Практический пример: VIPER с Router
Рассмотрим классическую архитектуру VIPER, где Router — обязательная часть.
// 1. Протокол для интерфейса модуля "Список статей"
protocol ArticleListRouterProtocol: AnyObject {
func openArticleDetails(for article: Article)
}
// 2. Реализация Router'а для модуля "Список статей"
final class ArticleListRouter: ArticleListRouterProtocol {
weak var viewController: UIViewController?
func openArticleDetails(for article: Article) {
// Сборка следующего модуля
let detailsViewController = ArticleDetailsModuleBuilder.build(article: article)
// Выполнение перехода
viewController?.navigationController?.pushViewController(detailsViewController, animated: true)
}
}
// 3. Module Builder (Сборщик) - часто логика выносится сюда
enum ArticleDetailsModuleBuilder {
static func build(article: Article) -> UIViewController {
let view = ArticleDetailsViewController()
let interactor = ArticleDetailsInteractor(article: article)
let presenter = ArticleDetailsPresenter()
let router = ArticleDetailsRouter()
view.presenter = presenter
presenter.view = view
presenter.interactor = interactor
presenter.router = router
interactor.presenter = presenter
router.viewController = view
return view
}
}
// 4. Внутри ArticleListPresenter (или ViewModel)
class ArticleListPresenter {
var router: ArticleListRouterProtocol?
func didSelectArticle(_ article: Article) {
// Presenter не знает о навигации, он делегирует её Router'у
router?.openArticleDetails(for: article)
}
}
Паттерн Coordinator
В более современных подходах (особенно для приложений со сложной навигацией) используется паттерн Coordinator, который является эволюцией Router'а. Coordinator управляет группой экранов, представляющих собой отдельный поток (flow).
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
class MainCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let articleListVC = ArticleListViewController()
articleListVC.coordinator = self // ViewController держит weak ссылку на Coordinator
navigationController.pushViewController(articleListVC, animated: false)
}
func openArticleDetails(_ article: Article) {
let detailsCoordinator = ArticleDetailsCoordinator(
navigationController: navigationController,
article: article
)
detailsCoordinator.parentCoordinator = self
childCoordinators.append(detailsCoordinator)
detailsCoordinator.start() // Coordinator сам управляет своим flow
}
func childDidFinish(_ child: Coordinator?) {
// Логика очистки дочерних Coordinator'ов
for (index, coordinator) in childCoordinators.enumerated() {
if coordinator === child {
childCoordinators.remove(at: index)
break
}
}
}
}
Преимущества использования Router/Coordinator
- Тестируемость: Навигационную логику можно протестировать изолированно, без создания реальных ViewController'ов (мокая UINavigationController).
- Переиспользуемость: ViewController, не знающий о контексте навигации, можно легко встроить в другой поток приложения.
- Читаемость и поддержка: Код, отвечающий за переходы, собран в одном месте, а не размазан по
prepareForSegueилиIBActionво множестве контроллеров. - Гибкость при рефакторинге: Изменение порядка экранов или способа перехода (например, замена push на модальный показ) требует правок только в Router/Coordinator.
Таким образом, Router/Coordinator — это критически важный паттерн для создания масштабируемых iOS-приложений. Он превращает навигацию из импровизированного набора вызовов внутри контроллеров в декларативную, хорошо структурированную систему, что особенно ценно в крупных проектах с большой командой разработчиков.