Главная » Хабрахабр » Application Coordinator в iOS приложениях

Application Coordinator в iOS приложениях

Каждый год в платформе iOS происходит множество изменений, к тому же регулярно выходят сторонние библиотеки по работе с сетью, кэшированию данных, отрисовке UI через JavaScript и прочему. В противовес всем этим тенденциям Павел Гуров рассказал об архитектурном решении, которое будет актуально независимо от того, какими технологиями вы пользуетесь сейчас или будете пользоваться через пару лет.

Под катом демо и инструкция по максимально быстрому внедрению этого подхода. ApplicationCoordinator можно использовать для построения навигации между экранами, и заодно решить ряд проблем.

О спикере: Павел Гуров занимается разработкой iOS приложений в Avito.

Навигация

Это то, с чего вообще начинается приложение еще на этапе создания прототипа, когда вы даже до конца не знаете: как будут выглядеть экраны, какие будут анимации, будет ли кэширование данных. Навигация между экранами — это задача, с которой 100% из вас сталкивается, не важно, что вы делаете — социальную сеть, вызов такси или онлайн банк. То есть фактически сразу. Экраны могут быть пустыми или статичными картинками, но задача навигации появляется в приложении, как только этих экранов становится больше одного.

Еще там говорится о том, что модули могут знать друг о друге, общаться друг с другом и т.д. Наиболее распространенные методы построения архитектуры iOS приложений: MVc, MVVm и MVp, описывают то, как построить один экран-модуль. Но совсем мало внимания уделяется вопросам, как совершаются переходы между этими модулями, кто принимает решение об этих переходах, и как передаются данные.

UlStoryboard + segues

iOS из коробки предоставляет несколько способов показать следующий по сценарию экран:

  1. Всем известный UlStoryboard + segues, когда мы обозначаем все переходы между экранами в одном мета-файле, и потом их вызываем. Все очень удобно и здорово.
  2. Контейнеры (Containers) — такие, как UINavigationController. UITabBarController, UIPageController или, возможно, самописные контейнеры, которые можно использовать как программно, так и вместе со StoryBoards.
  3. Метод present(_:animated:completion:). Это просто метод класса UIController.

В самих этих инструментах проблем нет. Проблема в том, как именно они обычно используются. UINavigationController, performSegue, prepareForSegue, метод presentViewController — это все property-методы класса UIViewController. Apple предлагает пользоваться этими инструментами внутри самого UIViewController.

Доказательством этому служит следующее.

Написано прямо — если вы используете segues и вам нужно передать данные в следующий по сценарию экран, вы должны: достать этот ViewController из segue; знать, какого он будет типа; привести его к этому типу и передать туда свои данные. Это комментарии, которые появляются в вашем проекте, если вы создаете новый подкласс UIViewController по стандартному шаблону.

Такой подход к проблемам в построении навигации.

Жесткая связанность экранов    1.

Мало того, что он знает о его существовании, он его еще и потенциально создает, или берет из segue, зная, какого он типа, и передает ему какие-то данные. Это значит, что экран 1 знает о существовании экрана 2.

Все становится еще сложнее, если контроллеры 2 и 3 могут вызываться еще из нескольких мест, не только из экрана 1. Если нам понадобится при каких-то обстоятельствах показать вместо экрана 2 экран 3, то придется знание о новом экране 3 точно так же зашивать в контроллер экрана 1. Получается, что знание об экране 2 и 3 придется зашивать в каждое из этих мест.

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

Изменение порядка контроллеров сценария    2.

Чтобы поменять местами два ViewController, недостаточно будет зайти в UlStoryboard и поменять местами 2 картинки. Это тоже не так просто по причине связанности. Придется открывать код каждого из этих экранов, переносить его в настройки следующего, менять его местами, что не очень удобно.

Передача данных по сценарию    3.

Так как у нас изначально нет ничего, кроме ViewController, придется каким-то образом связать эти два ViewController — не важно как — через делегирование или как-то еще. Например, при выборе чего-то на экране 3, нам нужно обновить View на экране 1. Еще сложнее будет, если по действию на экране 3 нужно будет обновить не один экран, а сразу несколько, например, и первый, и второй.

Кто-то скажет, давайте использовать нотификацию, кто-то — через shared state. В таком случае делегированием обойтись не удастся, потому что делегирование — это связь один к одному. Все это затруднит отладку и отслеживание потоков данных в нашем приложении.

Давайте посмотрим на конкретный пример из настоящего приложения «Avito Услуги Pro». Как говорится, лучше 1 раз увидеть, чем 100 раз услышать. Это приложение для профессионалов-в сфере услуг, в котором удобно отслеживать свои заказы, общаться с заказчиками, искать новые заказы.

Сценарий — выбор города в редактировании профиля пользователя.

Нас интересует выбор города. Перед вами экран редактирования профиля, такой есть во многих приложениях.

Что здесь происходит?

  • Пользователь нажимает на ячейку с городом, и первый экран принимает решение, что пора в стек навигации добавить следующий экран. Это экран со списком федеральных городов (Москва и Санкт-Петербург) и список регионов.
  • Если пользователь на втором экране выбирает федеральный город, то второй экран понимает, что сценарий завершен, пересылает первому выбранный город и Navigation-стек откатывается до первого экрана. Сценарий считается завершенным.
  • Если же пользователь на втором экране выбирает область, то второй экран принимает решение о том, что нужно подготовить третий экран, в котором мы видим список городов этой области. Если пользователь выбирает какой-то город, то этот город отправляется на первый экран, откатывает Navigation-стек и сценарий считается завершенным.

На данной схеме проблемы связанности, о которых я говорил ранее, изображены в виде стрелок между ViewController. От этих проблем мы будем сейчас избавляться.

Как мы это делаем?

  1. Мы запрещаем себе внутри UIViewController обращаться к контейнерам, то есть к self.navigationController, self.tabBarController или еще каким-то кастомным контейнерам, которые вы сделали как property extension. Мы теперь не можем из кода экрана взять свой контейнер и попросить его что-то сделать.

  2. Мы запрещаем себе внутри UIViewController вызывать метод performSegue и писать код в методе prepareForSegue, который бы брал следующий по сценарию экран и занимался его настройкой. То есть мы больше не работаем c segue (с переходами между экранами) внутри UIViewController.

  3. Мы точно также запрещаем любые упоминания о других контроллерах внутри нашего конкретного контроллера: никаких инициализаций, передач данных и этого всего.

Координатор

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

Сейчас не думайте о том, как он реализован, просто посмотрите, как в этом случае изменится сценарий выбора города. Координатор — это просто обычный объект, которому мы передаем на старте NavigationController и вызываем метод Start.

Координатор понимает, что в NavigationController пора запушить первый экран, что он и делает. Теперь он начинается не с того, что мы готовим переход на какой-то конкретный экран NavigationСontroller, а мы у координатора вызываем метод Start, передав ему перед этим в инициализаторе NavigationСontroller.

То есть экран сам ничего не знает — после него, как говорится, хоть потоп. Дальше, когда пользователь выбирает ячейку с городом, это событие передается наверх координатору. Он это сообщение передает координатору, и дальше координатор реагирует на это тем (так как у него есть NavigationController), что отправляет в него следующий шаг — это выбор регионов.

Дальше пользователь нажимает «Регион» — такая же точно картина — экран сам ничего не решает, только сообщает координатору, которые открывает следующий экран.

То есть координатору передается сообщение, что был выбран город. Когда на третьем экране пользователь выбирает уже конкретный город, этот город передается на первый экран тоже через координатор. Координатор отправляет это сообщение в первый экран и откатывает Navigation-стек до первого экрана.

Более того, они вообще ничего не знают о своем окружении. Обратите внимание, что контроллеры теперь не общаются друг с другом, решая, кто будет следующий, и не передают друг другу никакие данные.

Если рассмотреть приложение в рамках трехслойной архитектуры, то ViewController в идеале должны полностью поместиться в слой Presentation и нести в себе как можно меньше логики приложения.

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

Демо

Презентация и демопроект доступен на Github, ниже демонстрация во время доклада.

Это тот же самый сценарий: редактирование профиля и выбор в нем города.

Он показывает информацию о текущем пользователе: имя и выбранный город. Первый экран — это экран редактирования юзера. Когда мы нажимаем на нее, попадаем на экран со списком городов. Есть кнопка «Выбрать город». Если мы выбираем там какой-то город, то первый экран получает этот город.

Начнем с модели. Давайте посмотрим теперь, как это устроено в коде.

struct City { let name: String
} struct User { let name: String var city: City?
}

Модели простые:

  1. Структура город, у которой есть поле имя, строка;
  2. Пользователь, у которой тоже есть имя и property город.

Дальше — StoryBoard. Он начинается с NavigationController. В принципе, здесь те же самые экраны, которые были в симуляторе: экран редактирования пользователя с лейблом и кнопкой и экран со списком городов, на котором показана табличка с городами.

Экран редактирования пользователя

import UIKit final class UserEditViewController: UIViewController, UpdateableWithUser } // MARK: - Output - var onSelectCity: (() -> Void)? @IBOutlet private weak var userLabel: UILabel? @IBAction private func selectCityTap(_ sender: UIButton) { onSelectCity?() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateView() } private func updateView() { userLabel?.text = "User: \(user?.name ?? ""), \n" + "City: \(user?.city?.name ?? "")" }
}

Здесь есть property User — это тот user, который передается снаружи — пользователь, которого будем редактировать. Set user сюда приводит к тому, что вызывается блок didSet, что приводит к вызову локального метода updateView(). Все, что делает этот метод — просто помещает информацию о пользователе в лейбл, то есть показывает его имя и название города, в котором этот пользователь живет.

То же самое происходит в методе viewWillAppear().

Здесь контроллер сам ничего не решает: не создает никакие контроллеры, не вызывает никакие segue. Самое интересное место — это обработчик нажатия на кнопку выбора города selectCityTap(). Коллбэк onSelectCity не имеет параметров. Все, что он делает, это вызывает коллбэк — это второй свойство нашего ViewController. Когда пользователь нажимает кнопку, это приводит к тому, что вызывается этот коллбэк.

Экран выбора города

import UIKit final class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? // MARK: - Private variables - private let cities: [City] = [City(name: "Moscow"), City(name: "Ulyanovsk"), City(name: "New York"), City(name: "Tokyo")] // MARK: - Table - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cities.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = cities[indexPath.row].name return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { onCitySelected?(cities[indexPath.row]) }
}

Этот экран — это UITableViewController. Список городов здесь фиксированный, но он может приходить откуда-то из другого места. Далее (// MARK: — Table -) достаточно тривиальный табличный код, который показывает список городов в ячейках.

Здесь экран опять сам ничего не решает. Самое интересное место здесь — это обработчик didSelectRowAt IndexPath, всем хорошо известный метод. Он просто вызывает коллбэк с единственным параметром «город». Что происходит дальше, после того как выбран город?

Как мы видим, они ничего о своем окружении не знают. На этом код самих экранов заканчивается.

Координатор

Перейдем к связующему звену между этими экранами.

import UIKit protocol UpdateableWithUser: class { var user: User? { get set }
} final class UserEditCoordinator { // MARK: - Properties private var user: User { didSet { updateInterfaces() } } private weak var navigationController: UINavigationController? // MARK: - Init init(user: User, navigationController: UINavigationController) { self.user = user self.navigationController = navigationController } func start() { showUserEditScreen() } // MARK: - Private implementation private func showUserEditScreen() { let controller = UIStoryboard.makeUserEditController() controller.user = user controller.onSelectCity = { [weak self] in self?.showCitiesScreen() } navigationController?.pushViewController(controller, animated: false) } private func showCitiesScreen() { let controller = UIStoryboard.makeCitiesController() controller.onCitySelected = { [weak self] city in self?.user.city = city _ = self?.navigationController?.popViewController(animated: true) } navigationController?.pushViewController(controller, animated: true) } private func updateInterfaces() { navigationController?.viewControllers.forEach { ($0 as? UpdateableWithUser)?.user = user } }
}

Координатор имеет два property:

  1. User — пользователь, которого мы будем редактировать;
  2. NavigationController, которому нужно передать при старте.

Eсть простой init(), который заполняет эти property.

Остановимся на нем поподробнее. Дальше есть метод start(), который приводит к тому, что вызывается метод ShowUserEditScreen(). Дальше проставляет коллбэк onSelectCity и пушит этот контроллер в Navigation-стек. Этот метод достает контроллер из UIStoryboard, передает ему нашего локального юзера.

После того, как пользователь нажал на кнопку, срабатывает коллбэк onSelectCity, и это приводит к тому, что вызывается следующий приватный метод ShowCitiesScreen().

Когда пользователь выбирает конкретный город, срабатывает этот коллбэк, координатор обновляет у нашего локального юзера поле «город» и откатывает Navigation-стек до первого экрана. На самом деле, он делает практически то же самое — поднимает немножко другой контроллер из UIStoryboard, проставляет ему коллбэк onCitySelected и пушит его в Navigation-стек — вот и все, что происходит.

Этот метод проходится по всему Navigation-стеку и пытается развернуть каждый ViewController как протокол UpdateableWithUser. Так как User — это структура, то обновление поля «город» у неё приводит к тому, что вызывается блок didSet, соответственно вызывается приватный метод updateInterfaces(). Если это удается, то он прокидывает его обновленному юзеру. Это простейший протокол, у которого есть только одно свойство — user. Таким образом получается, что наш выбранный юзер на втором экране автоматически прокидывается на первый экран.

Это то, где все это начинается. С координатором все понятно, и единственное, что осталось здесь показать, это точку входа в наше приложение. В данном случае это метод didFinishLaunchingWithOptions нашего AppDelegate.

import UIKit @UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var coordinator: UserEditCoordinator! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { guard let navigationController = window?.rootViewController as? UINavigationController else { return true } let user = User(name: "Pavel Gurov", city: City(name: "Moscow")) coordinator = UserEditCoordinator(user: user, navigationController: navigationController) coordinator.start() return true }
}

Здесь navigationController достается из UIStoryboard, создается User, которого мы будем редактировать, с именем и конкретным городом. Дальше создается наш координатор с User и navigationController. У него вызывается метод start(). Координатор передается в локальные property — вот, в принципе, и все. Схема достаточно простая.

Inputs and outputs

Есть несколько моментов, на которых я бы хотел остановиться подробнее. Вы наверняка обратили внимание, что property в userEditViewController помечен комментарием, как Input, а коллбэки этих контроллеров помечены, как Output.

Например, в UserEditViewController это property User — может измениться сам User или его параметр City. Вход — это любые данные, которые могут измениться со временем, а также какие-то методы ViewController, которые можно вызвать снаружи.

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

Но в Swift с этим все гораздо проще. В Objective-C я не очень любил писать сохранение коллбэков из-за их ужасного синтаксиса. Только тут, вместо того чтобы обозначать методы в протоколе и говорить, что координатор соответствует этому протоколу, и потом где-то отдельно писать эти методы, мы сразу можем очень удобно в одном месте создать сущность, проставить ей коллбэк и все это сделать. Использование коллбэков в данном случае — это альтернатива известного паттерна делегирования в iOS.

Правда, при таком подходе, в отличие от делегирования, появляется жесткая связанность между сущностью координатора и экраном, потому что координатор знает, что существует конкретная сущность экрана.

От этого можно избавиться таким же образом, как и в делегировании, с помощью протоколов.

Чтобы избежать связанности, мы можем закрыть Input и Output нашего контроллера протоколом.

Слева — аналог этой схемы на Swift. Выше протокол CitiesOutput, у которого есть ровно одно требование — наличие коллбэка onCitySelected. Делаем мы это для того, чтобы координатор не знал о существовании класса CitiesViewController. Наш контроллер соответствует этому протоколу, определяя у себя необходимый коллбэк. Для того, чтобы все это провернуть, добавляем в координатор фабрику. Но в какой-то момент ему понадобится сконфигурировать output у этого контроллера.

Получается, что наш координатор не создает контроллер и не получает его откуда-то. У фабрики есть метод cityOutput(). Ему прокидывается фабрика, которая возвращает в методе закрытый протоколом объект, и он ничего не знает о том, какого класса этот объект.

Зачем нам встраивать еще один дополнительный уровень, когда и так не было никаких проблем? Теперь самое главное — зачем вообще все это делать?

Если бы в нашем приложении выбор города был не в одном месте, а в разных координаторах, в разных сценариях, нам пришлось в каждое место зашивать флажок, прокидывать его снаружи, по этому флажку поднимать либо один, либо другой ViewController. Можно представить такую ситуацию: к нам придет менеджер и попросит сделать A/B-тестирование того, что вместо списка городов у нас появился бы выбор города на карте. Это не очень удобно.

Поэтому можно было бы сделать это в одном месте. Мы хотим из координатора это знание убрать. У них у обоих был бы коллбэк onCitySelected, и координатору было бы, в принципе, не важно, с каким из этих экранов работать — с картой или списком. В самой фабрике мы бы сделали параметр, по которому фабрика возвращает закрытый протоколом либо тот, либо другой контроллер.

Composition VS Inheritance

Следующий момент, на котором хотелось остановиться, это композиция против наследования.

  1. Первый метод, как можно сделать наш координатор — это сделать композицию, когда NavigationController передается ему снаружи и хранится локально как property. Это как бы композиция — мы в нее добавили NavigationController как property.
  2. С другой стороны, существует мнение, что в UI Kit и так все есть, и нам не нужно изобретать велосипед. Можно просто взять и наследовать UINavigationController.

Каждый вариант имеет свои плюсы и минусы, но лично мне кажется, что композиция в данном случае подходит больше, чем наследование. Наследование вообще в принципе менее гибкая схема. Если нам потребуется, например, изменить Navigation на, скажем, UIPageController, то мы сможем в первом случае просто закрыть их общим протоколом, типа «Покажи следующий экран» и удобно подставлять нужный нам контейнер.

Получается, что у него меньше шансов оступиться. С моей точки зрения, самый главный аргумент заключается в том, что вы скрываете от конечного пользователя в композиции все ненужные ему методы. У него нет возможности вызвать метод PushViewController, PopViewController, то есть как-то вмешаться в деятельность самого координатора. Вы оставляете только тот API, который необходим, например, метод Start — и все. Все методы родительского класса скрыты.

Storyboards

Я считаю, что они заслуживают отдельного внимания вместе с segues. Лично я поддерживаю segues, так как они позволяют визуально быстро ознакомиться со сценарием. Когда приходит новый разработчик, ему не нужно лазить по коду, Storyboards в этом помогают. Даже если вы делаете интерфейс с кодом, вы можете оставить пустые ViewController, и верстать интерфейс с кодом, но оставить хотя бы переходы и всю суть. Вся суть Storyboards именно в самих переходах, а не в верстке UI.

Мы можем спокойно использовать координаторы вместе с segues. К счастью, подход с координаторами не ограничивает в выборе инструментов. Но нужно помнить, что теперь мы не можем работать с segues внутри UIViewController.

Вместо того, чтобы делать что-то внутри контроллера, мы будем делегировать эти задачи опять же координатору, через коллбэк. Поэтому мы должны в нашем классе переопределить метод onPrepareForSegue. Вы просто прокидываете это все в коллбэк, а координатор там разберется. Вызывается метод onPrepareForSegue, вы сами ничего не делаете — вы не знаете, что это за segue, какой там destination контроллер — вам это все не важно. У него есть это знание, вам это знание ни к чему.

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

Тогда можно сильно все упростить, сделать вообще один класс — StoryboardCoordinator, и у него генерировать параметр RootType, в Storyboard делать начальным контроллером Navigation и заворачивать в него весь сценарий. Еще одна вещь, которую я нахожу удобной со Storyboard — это придерживаться правила, что один Storyboard равен одному координатору.

При инициализации мы передаем ему не конкретный navigationController, а Storyboard, из которого достается наш корневой Navigation и его первый контроллер. Как вы видите, здесь у координатора есть 2 property: navigationController; rootViewController нашего RootType generic-типа. То есть вы создали координатор, у него сразу есть Navigation, и сразу есть Root. Таким образом нам даже не нужно будет вызывать никаких методов Start. Вы можете либо Navigation показать модально, либо взять Root и запушить в существующую навигацию и дальше работать.

Наш UserEditCoordinator в таком случае стал бы просто typealias, подставляющим в generic-параметр тип своего RootViewController.

Передача данных обратно по сценарию

Поговорим о решении последней проблемы, которую я обозначил вначале. Это передача данных обратно по сценарию.

Чтобы показать пользователю, что он выбрал несколько городов внутри одной области, мы будем на экране со списком областей показывать маленькую циферку рядом с названием области, отображающую количество городов, выбранных в этой области. Рассмотрим тот же самый сценарий выбора города, но теперь в нем можно будет выбирать не один город, а несколько.

То есть и в первом мы должны в ячейке с городом показать, и во втором мы должны все цифры обновить у выбранных регионов. Получается, что действие на одном контроллере (на третьем) должно приводить к изменению внешнего вида сразу нескольких других.

Координатор упрощает эту задачу тем, что передает данные назад по сценарию — это теперь такая же простая задача, как и передача данных вперед по сценарию.

Пользователь выбирает какой-то город. Что здесь происходит? Координатор, как я уже показывал в демо, проходится по всему navigation-стеку и всем заинтересованным лицам передает обновленные данные. Это сообщение отправляется координатору. Соответственно, ViewController могут обновить свой View с этими данными.

Рефакторинг существующего кода

Как рефакторить существующий код, если вы хотите внедрить этот подход в уже существующее приложение, где есть MVc, MVVm или MVp?

Первое, что нужно сделать — разделить их на сценарии, в которых они участвуют. У вас есть пачка ViewController. В нашем примере есть 3 сценария: авторизация, редактирование профиля, лента.

Мы должны иметь возможность, на самом деле, стартовать эти сценарии из любого места в нашем приложении. Каждый сценарий мы теперь заворачиваем внутрь своего координатора. В этом должна быть гибкость — координатор должен быть полностью самодостаточен.

Оно заключается в том, что если вы в данный момент работаете с конкретным сценарием, вам не нужно каждый раз при запуске до него докликивать. Такой подход в разработке дает дополнительное удобство. Вы можете его при старте быстренько стартануть, в нем что-то править, а потом этот временный старт убрать.

После того, как мы определились с нашими координаторами, нужно определить, какой сценарий может привести к старту другого, и из этих сценариев составить дерево.

Здесь почти все встает на свои места, но остается очень важная деталь — у нашей схемы не хватает точки входа. В нашем случае дерево простое: LoginCoordinator может стартовать координатор редактирования профиля.

Его создает и стартует AppDelegate, и дальше он уже управляет логикой на уровне приложения, то есть тем, какой координатор стартует прямо сейчас. Этой точкой входа будет особый координатор — ApplicationCoordinator.

С координаторами в принципе можно сделать то же самое. Только что мы рассматривали очень похожую схему, только на ней вместо координаторов были ViewController, и мы делали так, чтобы ViewController ничего друг о друге не знали и не передавали друг другу данных. Координаторы становится независимыми, переиспользуемыми и легко тестируемыми. Мы можем обозначить у них некий Input (метод Start) и Output (коллбэк onFinish). Координаторы перестают знать друг о друге и общаются, например, только с ApplicationCoordinator.

Тут надо уже смотреть —возможно, разбить координаторы на подкоординаторы, то есть продумать такую архитектуру, чтобы эти объекты не разрастались до невероятных размеров. Нужно быть осторожным, потому что если в вашем приложении будет достаточно много этих сценариев, то ApplicationCoordinator может превратиться в огромный god-объект, будет знать о всех существующих сценариях — это тоже не очень здорово. Хотя размер — это не всегда повод для рефакторинга.

Откуда начать

Я советую начинать снизу вверх — сначала реализовать отдельные сценарии.

То есть пока у вас нет ни Root, ни других координаторов, вы можете делать один координатор и, как временное решение, стартануть его из UIViewController, сохранив его локально в property (как выше есть nextCoordinator). Как временное решение их можно стартовать внутри UIViewController. Все очень просто. Когда происходит какое-то событие, вы, как я показывал в демо, создаете локальное property, кладете туда координатор и вызываете у него метод Start.

У вас есть локальное property или какой-то массив зависимостей типа coordinator, вы туда все это складываете, чтобы никуда не убежало, и вызываете метод Start. Потом, когда уже сделали все эти координаторы, старт одного внутри другого выглядит абсолютно точно также.

Итог

  • Независимые экраны и сценарии, которые ничего друг о друге не знают, друг с другом не общаются. Мы этого и пытались добиться.
  • Легко менять порядок экранов в приложении без изменения кодов экранов. Если все сделано, как нужно, единственное, что должно измениться в приложении при изменении сценария, это не код экранов, а код координатора.
  • Упрощается передача данных между экранами и другие задачи, которые подразумевают под собой связь между экранами.
  • Лично мной самый любимый момент — чтобы начать его применять, вам не нужно добавлять в проект никаких сторонних зависимостей и разбираться в чужом коде.

Скорее изучай расписание (или обзор по нему) и бронируй билеты. AppsConf 2018 уже 8 и 9 октября — не пропусти! Естественно большое внимание обеим платформам — iOS и Android, плюс к этому доклады по архитектуре, которые не привязаны только к одной технологии, и обсуждение других важных вопросов, связанных с миром вокруг мобильной разработки.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Клонируем бесконтактную карту с помощью мобильного приложения

Всегда было интересно посмотреть, что происходит у банковской карточки под «капотом». Как реализуется протокол общения банковской карточки и POS-терминала, как это работает и насколько это безопасно. Такая возможность предстала передо мной, когда я проходил стажировку в компании Digital Security. В ...

Чудесный форпост на орбите

20 ноября исполнится 20 лет Международной космической станции. Не сомневаюсь, что будет множество рассказов про ее историю, конструкцию и эксплуатацию. Повторять одно и то же не хочется, и я решил поразмышлять, что будет самым важным для нас из опыта, который ...