Что такое паттерн Coordinator в iOS-разработке?

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

Ответ

Coordinator — это архитектурный паттерн, который инкапсулирует логику навигации между экранами (ViewController'ами) в отдельные объекты-координаторы. Его основная цель — освободить ViewController'ы от ответственности за знание о других экранах и переходы между ними, что повышает переиспользуемость, тестируемость и управляемость потоков приложения.

Ключевые принципы:

  1. Разделение ответственности: ViewController отвечает только за свою view и обработку пользовательских действий, делегируя решение о навигации координатору.
  2. Иерархия координаторов: Координаторы могут создавать и управлять дочерними координаторами, формируя дерево, соответствующее навигационным потокам приложения (например, MainCoordinator -> AuthCoordinator -> ProfileCoordinator).
  3. Управление зависимостями: Координатор часто выступает в роли «сборщика» (assembler), внедряя зависимости в создаваемые им ViewController'ы.

Пример базовой реализации:

protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    var navigationController: UINavigationController { get set }
    func start()
}

class MainCoordinator: Coordinator {
    var childCoordinators = [Coordinator]()
    var navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func start() {
        let viewController = HomeViewController()
        viewController.coordinator = self // Слабая ссылка!
        navigationController.pushViewController(viewController, animated: false)
    }

    func showDetails(for item: Item) {
        // Создание дочернего координатора для потока деталей
        let detailCoordinator = DetailCoordinator(
            navigationController: navigationController,
            item: item
        )
        detailCoordinator.parentCoordinator = self
        childCoordinators.append(detailCoordinator)
        detailCoordinator.start()
    }

    func childDidFinish(_ child: Coordinator?) {
        // Очистка завершенного дочернего координатора
        for (index, coordinator) in childCoordinators.enumerated() {
            if coordinator === child {
                childCoordinators.remove(at: index)
                break
            }
        }
    }
}

Преимущества паттерна:

  • Чистые ViewController'ы: Они не содержат navigationController.push/pop вызовов.
  • Централизованная навигация: Легко отслеживать и изменять потоки приложения.
  • Повторное использование: Один и тот же ViewController может быть использован в разных навигационных контекстах.
  • Упрощенное тестирование: Логику навигации можно тестировать изолированно от UIKit.