Что такое паттерн Coordinator в 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.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши координаторы, блядь! Слушай, как будто без них ViewController'ы — это такие беспомощные дети, которые сами в соседнюю комнату сходить не могут, обязательно за ручку вести. Ёпта, целая философия выросла!

Так, смотри, в двух словах, чтобы даже мартышке было понятно. Координатор — это такой отдельный мудак, который берёт на себя всю геморройную работу по перетаскиванию пользователя с экрана на экран. Твои ViewController'ы теперь могут расслабиться, как боги, и заниматься только тем, что на кнопки тыкают и данные показывают. А как только им надо куда-то перейти, они просто кричат этому самому координатору: «Эй, папаша, сведи меня с той красивой вьюшкой из соседнего модуля!». И всё.

Основные пиздели, на которых всё держится:

  1. Раздели обязанности, тварь! Вьюконтроллер — рисует кнопки и ругается матом, когда юзер криво вводит данные. Координатор — решает, куда этого юзера после этого отправить: то ли на следующий экран, то ли в баню. Каждый делает своё, не лезет в чужой монастырь со своим уставом.
  2. Иерархия, блядь, как в мафии. Есть главный координатор-босс. У него есть подчинённые — дочерние координаторы. Один отвечает за авторизацию, другой — за ленту новостей, третий — за профиль. Если нужно углубиться в какой-то поток, босс поручает это своему шестёрке, а сам ждёт результата. Красота!
  3. Он же сборщик, ёпта! Часто этот тип ещё и знает, какие зависимости (сервисы, фабрики, данные) нужно впихнуть в каждый новый экран. Получается такой универсальный папа Карло, который не только выстругивает Буратино (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'ы — чистые, как слёзы младенца. Никаких pushViewController или present у них внутри. Они просто делегируют. Красота, блядь!
  • Вся навигация — как на ладони. Где-то заебало, как переходят? Идешь к координатору этого потока и меняешь в одном месте. Не надо рыскать по 20 файлам.
  • Вьюху можно тыкать куда угодно. Один и тот же экран деталей теперь можно запустить и из ленты, и из поиска, и из уведомления. Он не привязан к конкретному способу открытия.
  • Тестировать — одно удовольствие. Можно написать тест, где ты просто вызываешь методы координатора и проверяешь, какие вьюхи он создаёт, не поднимая весь ебучий UIKit. Мечта, а не жизнь!

Вот и вся магия. Сначала кажется, что овердохуища кода, но когда проект вырастает до размеров небольшого государства — начинаешь молиться на того умника, который это придумал.