Откуда Router возьмет зависимости, необходимые для ViewModel?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура с 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
- Избегайте синглтонов в Router — передавайте зависимости явно
- Используйте протоколы для абстрагирования зависимостей
- Тестируемость — Router должен получать зависимости, которые можно легко мокировать
- Слабая связанность — Router не должен знать детали реализации зависимостей
- Жизненный цикл — учитывайте, кто владеет зависимостями (singleton, new instance)
Заключение
Router получает зависимости для ViewModel через:
- DI-контейнеры (Swinject, Needle)
- Фабрики/билдеры модулей
- Родительские компоненты в иерархических архитектурах
- Явную передачу от предыдущего модуля
Ключевой принцип: Router выступает как assembler модуля, но не как creator зависимостей. Все зависимости должны быть предоставлены Router извне через четкие контракты, что обеспечивает тестируемость, гибкость и соблюдение принципа единственной ответственности.