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

Откуда Router возьмет зависимости, необходимые для ViewModel?

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

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

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

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

Архитектура с Router и внедрение зависимостей в ViewModel

В современных iOS-архитектурах (VIPER, Clean Swift, RIBs и др.) Router действительно часто отвечает за создание модулей и передачу зависимостей в ViewModel (или Interactor/Presenter). Давайте разберем типичные подходы.

Основные источники зависимостей для Router

1. Через Dependency Injection Container

Наиболее распространенный способ — использование контейнера зависимостей (Swinject, Needle, собственный DI-контейнер).

// Пример со Swinject
class ProfileRouter: RouterProtocol {
    private let container: Container
    
    init(container: Container) {
        self.container = container
    }
    
    func showProfile(userId: String) {
        // Резолвим зависимости из контейнера
        guard let viewModel = container.resolve(ProfileViewModel.self, argument: userId),
              let viewController = container.resolve(ProfileViewController.self, argument: viewModel) else {
            return
        }
        
        navigationController?.pushViewController(viewController, animated: true)
    }
}

2. Через фабрики/билдеры

Router получает фабрику, которая инкапсулирует логику создания модуля.

protocol ProfileModuleFactory {
    func makeProfileModule(userId: String) -> UIViewController
}

class AppRouter: RouterProtocol {
    private let profileFactory: ProfileModuleFactory
    
    init(profileFactory: ProfileModuleFactory) {
        self.profileFactory = profileFactory
    }
    
    func navigateToProfile(userId: String) {
        let module = profileFactory.makeProfileModule(userId: userId)
        navigationController?.pushViewController(module, animated: true)
    }
}

3. Прямая передача из родительского модуля

В иерархических архитектурах (RIBs) зависимости передаются от родительского Router к дочернему.

// RIBs-like подход
protocol ProfileDependency: Dependency {
    var userService: UserService { get }
    var analyticsService: AnalyticsService { get }
}

final class ProfileComponent: Component<ProfileDependency> {
    var userId: String
    
    init(dependency: ProfileDependency, userId: String) {
        self.userId = userId
        super.init(dependency: dependency)
    }
    
    var viewModel: ProfileViewModel {
        return ProfileViewModel(
            userId: userId,
            userService: dependency.userService,
            analyticsService: dependency.analyticsService
        )
    }
}

Ключевые принципы передачи зависимостей

Инверсия зависимостей

Router не должен создавать зависимости напрямую, а получает их через абстракции:

protocol ViewModelFactory {
    func makeProfileViewModel(userId: String) -> ProfileViewModel
}

class Router {
    private let viewModelFactory: ViewModelFactory
    
    init(viewModelFactory: ViewModelFactory) {
        self.viewModelFactory = viewModelFactory
    }
}

Разделение ответственности

  • Router: навигация, сборка модуля
  • DI-система: управление жизненным циклом зависимостей
  • ViewModelFactory: создание ViewModel с конкретными зависимостями

Практический пример с полным циклом

// Конфигурация DI-контейнера
func setupDependencies() {
    container.register(NetworkService.self) { _ in
        return NetworkServiceImpl()
    }.inObjectScope(.container)
    
    container.register(ProfileViewModel.self) { (r, userId: String) in
        let networkService = r.resolve(NetworkService.self)!
        let analytics = AnalyticsService()
        return ProfileViewModel(
            userId: userId,
            networkService: networkService,
            analytics: analytics
        )
    }
}

// Router с инъекцией контейнера
class MainRouter {
    private let container: Container
    private weak var navigationController: UINavigationController?
    
    init(container: Container, navigationController: UINavigationController) {
        self.container = container
        self.navigationController = navigationController
    }
    
    func showProfile(for userId: String) {
        // Создаем ViewModel с зависимостями
        let viewModel = container.resolve(
            ProfileViewModel.self,
            argument: userId
        )!
        
        // Создаем ViewController
        let viewController = ProfileViewController(viewModel: viewModel)
        
        // Выполняем навигацию
        navigationController?.pushViewController(
            viewController, 
            animated: true
        )
    }
}

Рекомендации и best practices

  1. Избегайте синглтонов в Router — передавайте зависимости явно
  2. Используйте протоколы для абстрагирования зависимостей
  3. Тестируемость — Router должен получать зависимости, которые можно легко мокировать
  4. Слабая связанность — Router не должен знать детали реализации зависимостей
  5. Жизненный цикл — учитывайте, кто владеет зависимостями (singleton, new instance)

Заключение

Router получает зависимости для ViewModel через:

  • DI-контейнеры (Swinject, Needle)
  • Фабрики/билдеры модулей
  • Родительские компоненты в иерархических архитектурах
  • Явную передачу от предыдущего модуля

Ключевой принцип: Router выступает как assembler модуля, но не как creator зависимостей. Все зависимости должны быть предоставлены Router извне через четкие контракты, что обеспечивает тестируемость, гибкость и соблюдение принципа единственной ответственности.