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

Где разместить связь между Coordinator и экраном?

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

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

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

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

Где разместить связь между 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-приложений со сложной навигацией.

Где разместить связь между Coordinator и экраном? | PrepBro