Ответ
Связь между 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. Это делает его центральным узлом для тестирования и изменения навигации.
Ответ 18+ 🔞
Смотри, тут вся философия в том, что один другого за жопу не держит, а просто кричит в темноту «Эй, я тут кнопку нажал!». А уже координатор, этот главный по цирку, решает, куда прыгнуть дальше. Вот как это на практике выглядит, без всей этой академической пурги.
1. Замыкания (Closures) — просто, как три копейки Координатор впендюривает во ViewModel или прямо в контроллер функции-прыгалки. Контроллер их дергает — и полетели.
// Координатор, он же режиссёр этой басни
func makeLoginScreen() -> UIViewController {
let viewModel = LoginViewModel()
// Скармливаем вьюмодели замыкания на все случаи жизни
viewModel.onLoginSuccess = { [weak self] user in
// Пользователь залогинился — гоним его на главную
self?.showHomeScreen(for: user)
}
viewModel.onSignUpTapped = { [weak self] in
// Захотел регистрироваться — получай форму
self?.showSignUpScreen()
}
return LoginViewController(viewModel: viewModel)
}
Контроллер при этом нихуя не знает про showHomeScreen или showSignUpScreen. Он просто, как обезьянка, тыкнул кнопку — и дернул onSignUpTapped(). А куда это приведёт — его, мартышку, не ебёт.
2. Протоколы/делегаты (Protocols/Delegates) — классика, для любителей формальностей Тут контроллер объявляет: «Слушай, я буду орать вот в эту трубу, а ты подставь ухо». Координатор подставляет.
// Это та самая труба, интерфейс для ора
protocol ProductListDelegate: AnyObject {
func didSelectProduct(_ product: Product)
func didTapFilters()
}
// Контроллер списка товаров
class ProductListViewController: UIViewController {
// Слабая ссылка, блядь! Чтобы не получить цикл и не накрыться медным тазом.
weak var delegate: ProductListDelegate?
private func productSelected(_ product: Product) {
// Произошло событие — орем в трубу
delegate?.didSelectProduct(product)
}
}
// Координатор реализует протокол и слушает
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 — для параноиков, которые хотят ещё один слой абстракции Выносим всю механику пушей-попов в отдельную сущность, а координатор ей рулит. Ну, типа, чтобы ещё чище было. Иногда это овердохуища кода, но для больших проектов бывает оправдано.
Главные заповеди, которые нарушать — себя не уважать:
[weak self]иweak var— это святое. Забудешь — получишь retain cycle, память потечёт, и приложение накроется тихо и беспощадно. Проверено на собственных костылях.- ViewController — тупая мартышка. Он не должен знать, какой экран следующий. Его удел — показать кнопки и заорать делегату или дернуть замыкание: «Хозяин, тут нажали!».
- Весь цирк с шапито — в координаторе. Последовательность экранов, переходы, передача данных — это всё его вотчина. Хочешь изменить поток — ковыряешь в одном месте, а не бегаешь по всем контроллерам, как угорелый.
Вот и вся магия. Никакой телепатии, просто один кричит, другой слушает и решает, куда идти. А то, бывает, напишут такое, что сам Тургенев с Муму в гробу перевернулся бы.