Где разместить связь между Coordinator и экраном?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Где разместить связь между Coordinator и экраном?
Связь между Coordinator и экраном в архитектуре с координаторами (Coordinator Pattern) размещается в точке создания и настройки экрана, обычно внутри метода start() конкретного координатора. Это ключевой аспект для обеспечения слабого связывания (loose coupling) и управления потоком навигации.
Основные подходы к установке связи
1. Связь через инициализатор ViewController
Наиболее чистый способ — передать координатор (или его методы) в инициализатор ViewController или его ViewModel (если используется MVVM). Это делает зависимость явной и упрощает тестирование.
// Coordinator
final class AuthCoordinator: Coordinator {
func start() {
let authService = AuthService()
let viewModel = LoginViewModel(authService: authService)
// Передаем замыкание для навигации в ViewModel
viewModel.onLoginSuccess = { [weak self] in
self?.showMainScreen()
}
viewModel.onForgotPassword = { [weak self] in
self?.showForgotPassword()
}
let viewController = LoginViewController(viewModel: viewModel)
navigationController.pushViewController(viewController, animated: true)
}
private func showMainScreen() { ... }
}
// ViewModel
final class LoginViewModel {
var onLoginSuccess: (() -> Void)?
var onForgotPassword: (() -> Void)?
func login() {
// Логика входа
onLoginSuccess?()
}
func forgotPassword() {
onForgotPassword?()
}
}
2. Связь через делегат (протокол)
Более формальный подход — создание протокола делегата, который координатор реализует. Это делает контракт явным и удобным для тестирования.
// Протокол для навигационных событий экрана Login
protocol LoginCoordinating: AnyObject {
func didLoginSuccessfully()
func showForgotPassword()
}
// Coordinator реализует протокол
extension AuthCoordinator: LoginCoordinating {
func didLoginSuccessfully() {
showMainScreen()
}
func showForgotPassword() {
let forgotCoordinator = ForgotPasswordCoordinator(navigationController: navigationController)
childCoordinators.append(forgotCoordinator)
forgotCoordinator.start()
}
}
// В ViewController или ViewModel
weak var coordinator: LoginCoordinating?
3. Связь через замыкания (Closures)
Популярный и гибкий подход — передача замыканий (closures) для навигационных событий. Это минимизирует boilerplate-код, но требует аккуратного управления циклами удержания.
// В координаторе при создании экрана
let viewController = ProfileViewController()
viewController.onEditProfile = { [weak self] user in
self?.showEditProfile(for: user)
}
viewController.onSettings = { [weak self] in
self?.showSettings()
}
Ключевые принципы размещения связи
- Инициализация в
start(): Связь устанавливается в момент создания экрана внутри методаstart()координатора. - Слабое связывание: Экран не должен знать о конкретном классе координатора, только о протоколе или замыкании.
- Управление памятью: Обязательное использование
[weak self]в замыканиях для предотвращения retain cycle. - Инкапсуляция потока: Весь сценарий навигации инкапсулирован в координаторе, экран только вызывает триггеры.
Полный пример реализации
// Протокол координатора
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
func start()
}
// Конкретный координатор
final class ProductListCoordinator: Coordinator {
private let navigationController: UINavigationController
var childCoordinators = [Coordinator]()
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let networkService = ProductNetworkService()
let viewModel = ProductListViewModel(networkService: networkService)
// Устанавливаем связь через замыкания
viewModel.onProductSelected = { [weak self] product in
self?.showProductDetail(product)
}
viewModel.onCartRequested = { [weak self] in
self?.showCart()
}
let viewController = ProductListViewController(viewModel: viewModel)
navigationController.pushViewController(viewController, animated: true)
}
private func showProductDetail(_ product: Product) {
let detailCoordinator = ProductDetailCoordinator(
navigationController: navigationController,
product: product
)
childCoordinators.append(detailCoordinator)
detailCoordinator.start()
}
private func showCart() {
let cartCoordinator = CartCoordinator(navigationController: navigationController)
childCoordinators.append(cartCoordinator)
cartCoordinator.start()
}
}
Вывод
Связь между Coordinator и экраном должна размещаться на этапе создания экрана в методе start() координатора, используя либо протоколы делегатов, либо замыкания, передаваемые в ViewController или ViewModel. Этот подход обеспечивает четкое разделение ответственности: координатор управляет навигацией и потоком приложения, а экран отвечает только за отображение данных и пользовательские взаимодействия. Это делает код более тестируемым, модульным и поддерживаемым, что критически важно для больших iOS-приложений со сложной навигацией.