Как Router в архитектуре (VIPER, Clean Swift) получает зависимости для сборки ViewModel или Presenter?

«Как Router в архитектуре (VIPER, Clean Swift) получает зависимости для сборки ViewModel или Presenter?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Router (или Assembly/Builder) получает зависимости для сборки модуля из внешнего DI-контейнера (Dependency Injection Container) или фабрики зависимостей. Он не создает зависимости сам, а лишь запрашивает их.

Основные подходы:

  1. Использование DI-контейнера (Swinject, Dip): Контейнер предварительно регистрирует все сервисы (сетевые, хранилища, аналитики). Router разрешает (resolve) зависимости из контейнера при сборке модуля.

    // Где-то при запуске приложения
    container.register(NetworkService.self) { _ in NetworkServiceImpl() }
    container.register(DataStorage.self) { _ in CoreDataStorage() }
    
    // В роутере
    class ProfileRouter {
        let container: Container // Контейнер внедряется в роутер
    
        func createModule() -> UIViewController {
            let view = ProfileViewController()
            // Резолвим ViewModel со всеми ее зависимостями из контейнера
            let viewModel = container.resolve(ProfileViewModel.self)!
            view.viewModel = viewModel
            return view
        }
    }
  2. Явная передача через инициализатор (Manual Dependency Injection): Зависимости передаются в роутер извне (например, из родительского модуля или координатора), а роутер передает их дальше во ViewModel/Presenter.

    class ProfileRouter {
        let service: NetworkService
        let storage: DataStorage
    
        init(service: NetworkService, storage: DataStorage) {
            self.service = service
            self.storage = storage
        }
    
        func createModule() -> UIViewController {
            let view = ProfileViewController()
            // Создаем ViewModel, передавая зависимости явно
            let viewModel = ProfileViewModel(service: service, storage: storage)
            view.viewModel = viewModel
            return view
        }
    }

Ключевая идея: Router выступает как компоновщик (assembler) модуля. Он знает, какие компоненты (View, ViewModel, Presenter) и какие зависимости нужны для их работы, но сами зависимости предоставляются извне, что обеспечивает тестируемость и соблюдение принципа инверсии зависимостей (DIP).