← Назад к вопросам

Для чего используешь Router?

2.0 Middle🔥 241 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Роль 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-приложений. Он превращает навигацию из импровизированного набора вызовов внутри контроллеров в декларативную, хорошо структурированную систему, что особенно ценно в крупных проектах с большой командой разработчиков.