Как организовать связь между Coordinator и ViewController в iOS?

«Как организовать связь между Coordinator и ViewController в iOS?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Связь между Coordinator и ViewController строится по принципу инверсии зависимостей: ViewController сообщает о событиях, а Coordinator принимает решения о навигации. Вот основные паттерны:

1. Через замыкания (Closures) — популярный и гибкий способ Coordinator внедряет замыкания во ViewModel или напрямую во ViewController.

// В Coordinator
func showLoginScreen() -> UIViewController {
    let viewModel = LoginViewModel()

    // ViewModel получает замыкание для навигации
    viewModel.onLoginSuccess = { [weak self] user in
        self?.showHomeScreen(for: user)
    }

    viewModel.onSignUpTapped = { [weak self] in
        self?.showSignUpScreen()
    }

    return LoginViewController(viewModel: viewModel)
}

2. Через протоколы/делегаты (Protocols/Delegates) ViewController объявляет делегата, который реализует Coordinator.

// Протокол для событий ViewController
protocol ProductListDelegate: AnyObject {
    func didSelectProduct(_ product: Product)
    func didTapFilters()
}

// ViewController хранит слабую ссылку на делегата
class ProductListViewController: UIViewController {
    weak var delegate: ProductListDelegate?

    private func productSelected(_ product: Product) {
        delegate?.didSelectProduct(product) // Сообщаем координатору
    }
}

// Coordinator реализует протокол
class AppCoordinator: ProductListDelegate {
    func start() -> UIViewController {
        let vc = ProductListViewController()
        vc.delegate = self // Устанавливаем связь
        return vc
    }

    // MARK: - ProductListDelegate
    func didSelectProduct(_ product: Product) {
        let detailVC = makeDetailVC(for: product)
        navigationController.pushViewController(detailVC, animated: true)
    }
}

3. Через Router (отдельный объект) Навигационная логика выносится в отдельный объект Router, которым управляет Coordinator.

Критически важные правила:

  • Всегда используйте [weak self] в замыканиях или weak var delegate, чтобы избежать циклов сильных ссылок (retain cycles).
  • ViewController не должен знать о других ViewController'ах. Он только сообщает о событии («пользователь нажал кнопку»).
  • Весь поток навигации (последовательность экранов) управляется Coordinator. Это делает его центральным узлом для тестирования и изменения навигации.