Прежде чем понять, что такое координаторы, нам сначала нужно увидеть, какую проблему они решают.
Рассмотрим простой процесс авторизации:
LoginScreen -> HomeScreen LoginScreen -> ForgotPasswordScreen
Типичная реализация для начинающих выглядит следующим образом:
class LoginViewController: UIViewController {
@IBAction func loginTapped() {
if loginSuccessful {
let homeVC = HomeViewController()
navigationController?.pushViewController(homeVC, animated: true)
}
}
@IBAction func forgotPasswordTapped() {
let forgotVC = ForgotPasswordViewController()
navigationController?.pushViewController(forgotVC, animated: true)
}
}
На первый взгляд, это кажется нормальным.
Но есть скрытые проблемы.
Теперь LoginViewController знает:
- Как создать HomeViewController
- Как создать ForgotPasswordViewController
- Как работает навигация
- Каким должен быть следующий экран
Это означает, что контроллер представления теперь управляет потоком приложения.
Это создает сильную взаимосвязь между экранами.
LoginViewController
|
| creates
v
HomeViewController
Эта взаимосвязь усиливается по мере развития приложения.
Реальный пример
Представьте себе реальное приложение для электронной коммерции:
ProductList
|
v
ProductDetail
|
+----> Reviews
|
+----> AddToCart
|
+----> SellerProfile
Если навигация находится внутри контроллеров представлений, каждый экран должен знать о следующем экране.
Вскоре ваш код будет выглядеть так:
ProductListVC | | pushes v ProductDetailVC | | pushes v ReviewVC
Теперь представьте:
- Изменения на экране сведений о товаре
- Изменения в последовательности действий на экране отзывов
- Изменения в последовательности действий в корзине
Вам необходимо изменить несколько контроллеров представления. Это нарушает ключевой принцип проектирования программного обеспечения.
Принцип единственной ответственности.
Контроллер представления должен управлять пользовательским интерфейсом, а не потоком навигации.
Идея паттерна Координатор
Паттерн Координатор отделяет логику навигации от контроллеров пользовательского интерфейса.
Вместо того чтобы контроллеры представления управляли навигацией, объект Координатор управляет потоком.
Контроллер представления только сообщает о действиях пользователя.
Координатор решает, что произойдет дальше.
Структура становится такой:
Coordinator
|
| creates
v
ViewController
Контроллеры представлений становятся проще.
Они лишь сообщают координатору о том, что произошло.
Пример:
User tapped Login button User selected product User requested password reset
Координатор отвечает за навигацию.
Базовая структура координатора
Простой координатор обычно содержит:
- Контроллер навигации
- Метод запуска
- Дочерние координаторы (необязательно)
Базовый протокол:
protocol Coordinator {
var navigationController: UINavigationController { get set }
func start()
}
Этот протокол определяет стандартную структуру для координаторов.
Создание AppCoordinator
Первый координатор в приложении обычно управляет начальным потоком.
AppCoordinator
|
v
LoginCoordinator
|
v
HomeCoordinato
Пример реализации:
class AppCoordinator: Coordinator {
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
showLogin()
}
private func showLogin() {
let loginVC = LoginViewController()
loginVC.delegate = self
navigationController.pushViewController(loginVC, animated: false)
}
}
Здесь координатор создает контроллер представления.
А не наоборот.
Контроллер представления без навигации
Теперь контроллер представления становится очень простым.
LoginViewController
Оно только отправляет действия пользователя.
Пример:
protocol LoginViewControllerDelegate: AnyObject {
func loginSuccessful()
func forgotPasswordTapped()
}
class LoginViewController: UIViewController {
weak var delegate: LoginViewControllerDelegate?
@IBAction func loginTapped() {
delegate?.loginSuccessful()
}
@IBAction func forgotPasswordTapped() {
delegate?.forgotPasswordTapped()
}
}
Обратите внимание на важную вещь.
В контроллере представления нет кода навигации. Нет push-уведомлений. Нет модальных окон. Нет контроллера навигации.
Контроллер просто сообщает о событиях.
Координатор обрабатывает навигацию
Теперь координатор решает, что произойдет дальше:
extension AppCoordinator: LoginViewControllerDelegate {
func loginSuccessful() {
showHome()
}
func forgotPasswordTapped() {
showForgotPassword()
}
private func showHome() {
let homeVC = HomeViewController()
navigationController.pushViewController(homeVC, animated: true)
}
private func showForgotPassword() {
let forgotVC = ForgotPasswordViewController()
navigationController.pushViewController(forgotVC, animated: true)
}
}
Теперь вся логика навигации сосредоточена в одном месте.
Координатор управляет потоком выполнения. ViewController управляет пользовательским интерфейсом.
Визуализация архитектуры
Без координаторов:
LoginVC ---> HomeVC | ---> ForgotPasswordVC
С Координатором:
Coordinator
/ \
v v
LoginVC ForgotVC
Контроллеры представлений не знают друг о друге. Они взаимодействуют только с координатором.
Дочерние координаторы
В больших приложениях часто содержится несколько потоков.
Примеры:
- Поток аутентификации
- Поток оформления заказа
- Поток настроек
Каждый поток может иметь свой собственный координатор. Пример структуры:
AppCoordinator
|
+---- AuthCoordinator
|
+---- MainCoordinator
|
+---- CheckoutCoordinator
Пример:
class AuthCoordinator: Coordinator {
var navigationController: UINavigationController
func start() {
showLogin()
}
private func showLogin() {
let vc = LoginViewController()
vc.delegate = self
navigationController.pushViewController(vc, animated: true)
}
}
Это обеспечивает изоляцию потоков.
Передача данных между экранами
Еще одно преимущество координаторов — более чистый поток данных.
Пример:
ProductList -> ProductDetail
С Координатором:
let detailVC = ProductDetailViewController() detailVC.product = selectedProduct navigationController?.pushViewController(detailVC)
Без него:
func productSelected(_ product: Product) {
coordinator?.showProductDetail(product)
}
Координатор:
func showProductDetail(_ product: Product) {
let vc = ProductDetailViewController(product: product)
navigationController.pushViewController(vc, animated: true)
}
Теперь поток данных централизован.
Управление памятью координаторов
Одна из проблем, с которой сталкиваются разработчики — освобождение координаторов. Если координаторы не удаляются должным образом, они остаются в памяти.
Пример:
var childCoordinators: [Coordinator] = []
Когда поток заканчивается:
childCoordinators.removeAll { $0 === coordinator }
Это гарантирует освобождение координаторов.
Когда следует использовать шаблон проектирования Координатор
Координаторы полезны, когда в вашем приложении сложные потоки навигации.
Пример:
- регистрация пользователя
- оформление заказа
- аутентификация
- глубокие ссылки
Они также полезны, когда:
- над приложением работают несколько команд
- потоки часто меняются
- тестирование логики навигации
Однако небольшим приложениям координаторы могут не понадобиться.
Распространенные ошибки разработчиков
1. Слишком большие координаторы
Некоторые разработчики помещают бизнес-логику внутрь координаторов. Это неправильно. Координаторы должны управлять только потоком навигации.
2. ViewControllers продолжают выполнять навигацию
Иногда разработчики оставляют push логику внутри контроллеров представлений.
Это противоречит цели использования координаторов.
3. Неправильный подход к управлению жизненным циклом координаторов
Если вы забудете удалить дочерние координаторы, то это может привести к утечкам памяти.
Реальные вопросы на собеседованиях по шаблону Координатор
На собеседованиях по iOS часто задают вопросы по архитектуре.
Вот некоторые из них.
Вопрос 1: Почему навигация внутри контроллеров представлений считается плохой практикой?
Хороший ответ: Контроллеры представлений должны фокусироваться на логике пользовательского интерфейса. Когда в них включается логика навигации, контроллеры становятся тесно связанными и сложными в поддержке.
Вопрос 2: Какую проблему решает шаблон Координатор?
Ответ: Он отделяет логику навигации от контроллеров представлений и централизует управление потоками.
Вопрос 3: Как контроллеры представлений взаимодействуют с координаторами?
Распространенные подходы:
Вопрос 4: Что такое дочерние координаторы?
Дочерние координаторы управляют конкретными потоками, такими как аутентификация или оформление заказа.
Они контролируются родительским координатором.
Вопрос 5: Заменяет ли шаблон «Координатор» MVC или MVVM?
Нет. Координатор работает совместно с архитектурными шаблонами.
Пример:
- Coordinator + MVVM
- Coordinator + MVC
- Coordinator + VIPER
Он только управляет навигацией.
Почему многие команды используют координаторы
Крупные компании предпочитают координаторы, потому что они хорошо масштабируются.
Преимущества — более чистые контроллеры представлений, централизованная логика навигации, лучшее разделение ответственности, более простое тестирование потоков навигации, более удобное сопровождение больших приложений.

