Что не следует добавлять в Coordinator?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что не следует добавлять в Coordinator
Coordinator (координатор) — это паттерн проектирования, который отвечает за управление потоком навигации и бизнес-логикой переходов между экранами в iOS-приложении. Он отделяет ответственность за навигацию от UIViewController, что улучшает модульность, тестируемость и поддержку кода. Однако важно понимать, что координатор — это не "мусорное ведро", куда можно сбрасывать любую логику. Его следует использовать строго по назначению.
Основные ошибки при реализации Coordinator
1. Бизнес-логика (Business Logic)
Координатор не должен содержать логику обработки данных, вычислений, сетевых запросов или работы с базой данных. Это ответственность сервисов, менеджеров, интеракторов (в архитектуре VIPER) или ViewModel (в MVVM). Координатор лишь инициирует переходы на основе результатов этой логики, полученных от других компонентов.
// ❌ НЕПРАВИЛЬНО: Coordinator занимается бизнес-логикой
class ProfileCoordinator {
func fetchUserData() {
APIManager.shared.fetchUser { [weak self] result in
switch result {
case .success(let user):
self?.updateUI(with: user) // Смешивание логики и навигации
case .failure:
self?.showError()
}
}
}
}
// ✅ ПРАВИЛЬНО: Координатор делегирует бизнес-логику сервису
class ProfileCoordinator {
let userService: UserServiceProtocol
func showProfile() {
userService.fetchUser { [weak self] result in
switch result {
case .success(let user):
self?.showProfileScreen(user: user) // Только навигация
case .failure(let error):
self?.showErrorScreen(error: error)
}
}
}
}
2. UI-логика и Data Source
Координатор не должен управлять элементами интерфейса, такими как наполнение таблиц (UITableViewDataSource), обновление UIView, анимации или обработка жестов. Это зона ответственности ViewController или View. Координатор может передавать данные в экран, но не должен заниматься их отображением.
// ❌ НЕПРАВИЛЬНО: Coordinator выступает как dataSource
class ListCoordinator: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count // Нарушение разделения ответственности
}
}
// ✅ ПРАВИЛЬНО: ViewController или отдельный объект управляют UI
class ListViewController: UIViewController {
var dataSource: ListDataSource!
// ...
}
3. Состояние приложения (App State)
Координатор не должен хранить глобальное состояние приложения (например, текущий пользователь, токены авторизации, настройки). Для этого существуют синглтоны, менеджеры состояний, Keychain или решения вроде Redux/SwiftUI State Management. Координатор может реагировать на изменения состояния (например, переход на экран логина при разлогине), но не быть его источником.
4. Жизненный цикл ViewController
Не следует вмешиваться в методы жизненного цикла UIViewController, такие как viewDidLoad, viewWillAppear, viewDidDisappear. Эти методы должны оставаться в самом ViewController или в связанных с ним компонентах (например, ViewModel в MVVM). Координатор может получать уведомления о событиях (через делегаты или замыкания) для управления навигацией, но не переопределять их.
5. Прямые зависимости от фреймворков
Избегайте жёсткой привязки к конкретным фреймворкам (кроме UIKit/SwiftUI для базовой навигации). Координатор не должен содержать код работы с CoreData, Firebase, Alamofire и т.д. Вместо этого используйте инъекцию зависимостей (Dependency Injection), передавая абстракции (протоколы).
// ❌ НЕПРАВИЛЬНО: Прямая зависимость от Alamofire
import Alamofire
class AuthCoordinator {
func login() {
AF.request("https://api.example.com/login").response { ... } // Жёсткая связь
}
}
// ✅ ПРАВИЛЬНО: Зависимость через протокол
protocol NetworkServiceProtocol {
func login(completion: @escaping (Result<User, Error>) -> Void)
}
class AuthCoordinator {
let networkService: NetworkServiceProtocol // Абстракция
func login() {
networkService.login { [weak self] result in
// Обработка результата для навигации
}
}
}
6. Управление памятью и ресурсами вне навигации
Координатор не должен заниматься освобождением ресурсов, не связанных напрямую с его жизненным циклом (например, отмена сетевых запросов, очистка кешей). Это задача соответствующих сервисов. Однако координатор обязан управлять памятью для дочерних координаторов и избегать циклов удерживания (retain cycles) через [weak self].
7. Жёсткая конфигурация UI
Избегайте прямой конфигурации UI-элементов (цвета, шрифты, констрейнты) внутри координатора. Используйте фабрики (Factory) или билдеры (Builder) для создания ViewController, либо передавайте параметры через инициализатор.
// ❌ НЕПРАВИЛЬНО: Координатор настраивает UI-элементы
class SettingsCoordinator {
func createSettingsViewController() -> UIViewController {
let vc = SettingsViewController()
vc.button.backgroundColor = .red // Нарушение принципа единственной ответственности
return vc
}
}
// ✅ ПРАВИЛЬНО: Использование фабрики или конфигуратора
class SettingsViewControllerFactory {
static func create() -> SettingsViewController {
let vc = SettingsViewController()
vc.configure(with: .default) // Конфигурация внутри ViewController
return vc
}
}
Принципы, которым следует придерживаться
- Единственная ответственность (Single Responsibility): Coordinator управляет только навигацией.
- Инъекция зависимостей (Dependency Injection): Передавайте сервисы через инициализатор.
- Слабая связность (Loose Coupling): Координатор не должен знать о внутренней реализации экранов.
- Протоколо-ориентированный подход: Используйте протоколы для абстракции.
Итог: Coordinator — это мощный паттерн для управления навигацией, но его сила в ограниченной и чёткой ответственности. Добавление лишней логики превращает его в "God Object", что усложняет тестирование, поддержку и нарушает архитектурные принципы. Сфокусируйтесь на том, чтобы координатор был "дирижёром", который управляет переходами, а не выполняет всю работу за оркестр.