Привет, читатель!

В этой статье я расскажу об архитектуре iOS приложений — Clean Swift. Мы рассмотрим основные теоретические моменты и разберем пример на практике.



Теория


Для начала разберем основную терминологию архитектуры. В Clean Swift приложение состоит из сцен, т.е. каждый экран приложения — это одна сцена. Основное взаимодействие в сцене идет через последовательный цикл между компонентами ViewController -> Interactor -> Presenter. Это называется VIP цикл.

Мостом между компонентами выступает файл Models, который хранит в себе передаваемые данные. Так же есть Router, отвечающий за переход и передачу данных между сценами, и Worker, который берет часть логики Interactor’a на себя.



View


Storyboard’ы, XIB’ы или UI элементы, написанные через код.

ViewController


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

Interactor


Содержит в себе бизнес логику сцены.

Он работает с сетью, базой данных и модулями устройства.

Interactor получает запрос из ViewController’a (с данными или пустой), обрабатывает его и, если это требуется, передает новые данные в Presenter.

Presenter


Занимается подготовкой данных для отображения.

Как пример, добавить маску на номер телефона или сделать первую букву в названии заглавной.
Обрабатывает данные, получение из Interactor’a, после чего отправляет их обратно во ViewController.

Models


Набор структур для передачи данных между компонентами VIP цикла. Каждый круг цикла имеет в себе 3 вида структур:

  • Request — Структура с данными (текст из TextField и т.д.) для передачи из ViewController'a в Interactor
  • Response — Структура с данными (загруженными из сети и т.д.) для передачи из Interactor в Presenter
  • ViewModel — Структура с обработанными данными (форматирование текста и т.д.) в Presenter’e для передачи обратно во ViewController

Worker


Разгружает Interactor, забирая на себя часть бизнес логики приложения, если Interactor стремительно разрастается.

Так же можно создавать общие для всех сцен Worker’ы, если их функционал используется в нескольких сценах.

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

Router


В Router выносится вся логика, отвечающая за переходы и передачу данных между сценами.



Для прояснения картины работы VIP цикла приведу стандартный пример — авторизация.

  1. Пользователь ввел свой логин и пароль, нажал на кнопку авторизации
  2. У ViewController срабатывает IBAction, после чего создается структура с введенными, в TextField’ы, данными пользователя (Models -> Request)
  3. Созданная структура передается в метод fetchUser в Interactor’e
  4. Interactor отправляет запрос в сеть и получает ответ об успешности авторизации
  5. На основе полученных данных, создает структуру с результатом (Models -> Response) и передается в метод presentUser в Presenter’e
  6. Presenter форматирует данные по необходимости и возвращает их (Models -> ViewModel) в метод displayUser в ViewController’e
  7. ViewController отображает полученные данные пользователю. В случае с авторизацией, может выводиться ошибка или срабатывать переход на другую сцену с помощью Router

Таким образом, мы получаем единую и последовательную структуру, с распределением обязанностей на компоненты.

Практика


А теперь разберем небольшой практический пример, который покажет как проходит VIP цикл. В этом примере мы сымитируем подгрузку данных при открытии сцены (экрана). Основные участки кода я пометил комментариями.

Весь VIP цикл завязан на протоколах, что обеспечивает возможностью подмены каких-либо модулей без нарушения работы приложения.
Для ViewController’a создается протокол DisplayLogic, ссылка на который передается в Presenter для последующего вызова. Для Interactor’a создаются два протокола BusinessLogic, отвечающий за вызов методов из ViewController’a, и DataSource, для хранения данных и передачу через Router в Interactor другой сцены. Presenter подписывается под протокол PresentationLogic, для вызова из Interactor’a. Связующим элементом всего этого выступает Models. Он содержит в себе структуры, с помощью которых идет обмен информаций между компонентами VIP цикла. С него и начнем разбор кода.



Models


В примере ниже, для сцены Home, я создал файл HomeModels, который содержит в себе набор запросов для VIP цикла.
Запрос FetchUser будет отвечать за подгрузку данных о пользователе, который мы и будем рассматривать дальше.


ViewController


При инициализации класса мы создаем экземпляры классов Interactor’a и Presenter’a этой сцены и устанавливаем зависимости между ними.
Далее во ViewController’e остается ссылка только на Interactor. С помощью этой ссылки мы будем создавать запрос к методу fetchUser(request:) в Interactor’е, для запуска VIP цикла.

Здесь стоит обратить внимание, как происходит запрос к Interactor. В методе loadUserInfromation() мы создаем экземпляр структуры Request, куда передаем начальное значение. Оно может быть взято из TextField, таблицы и так далее. Экземпляр структуры Request передается в метод fetchUser(request:), который находится в протоколе BusinessLogic нашего Interactor’a.


Interactor


Экземпляр класса Interactor’a содержит в себе ссылку на протокол PresentationLogic, под который подписан Presenter.

В методе fetchUser(request:), может содержаться любая логика подгрузки данных. Для примера я просто создал константы, с якобы полученными данными.

В этом же методе создается экземпляр структуры Response и заполняется, полученными ранее, параметрами. Response передается в PresentationLogic с помощью метода presentUser(response:). Другими словами, здесь мы получили сырые данные и передали их на обработку в Presenter.


Presenter


Имеет ссылку на протокол DisplayLogic, под который подписан ViewController. Не содержит никакой бизнес логики, а только форматирует полученные данные перед отображением. В примере мы отформатировали номер телефона, подготовили экземпляр структуры ViewModel и передали его на ViewController, с помощью метода displayUser(viewModel:) в протоколе DisplayLogic, где уже происходит отображение данных.


Заключение


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

Спасибо, что дочитали до конца.

Серия статей


  1. Общее представление об архитектуре Clean Swift (вы здесь)
  2. Router и Data Passing в архитектуре Clean Swift
  3. Workers в архитектуре Clean Swift
  4. Тестирование приложения на архитектуре Clean Swift
  5. Пример простого интернет-магазина на архитектуре Clean Swift

Все компоненты сцены: Ссылка
Помощь в написании статьи: Bastien

Комментарии (1)


  1. alexwillrock
    29.05.2019 14:45

    очень и очень спорно.
    Получается, что UIViewController просто проксирует модельки между презентером и UIView? тогда зачем он нужен? может UIViewController и сделать реализующим функции презентера?

    Далее — у нас есть Interactor и workers, скажем — у меня несколько workerов общих, которые используются в interactor, а interactor не имеет другой логики — он опять что-то проксирует, тогда зачем он нужен?

    А если использовать те же сервисы, то слоев проксирования возрастает кратно.
    По итогу — получаем ту же модель MVC, только C — контроллер размазан по максимальному количеству слоев выполняющим все все функции, M — модель в виде глупых структур данных (пассивная модель), ну и хотя бы V — View без логики внутри.

    Clean xyz — обычно происходит из книг статей Дядюшки Боба, но он не про такие слои писал… совсем не про это