Традиционным способом проектирования приложения под iOS является использование шаблона MVC (модель-представление-контроллер). Использование MVC для архитектуры приложения, может натолкнуть Вас на мысль, что каждый класс представляет собой модель, или представление, или контроллер. Поскольку значительная часть логики приложения не входит в модель или представление, она обычно оказывается в контроллере. Это приводит к проблеме, известной как Massive View Controllers, где контроллеры в конечном итоге делают слишком много. Если вся логика встроена в контроллер представления, это приводит к тестированию логики через UI, в свою очередь это является неправильным способом проектированиям логики. Также проще совмещать бизнес-логику и UI код в том же методе. Когда Вам будет нужно добавить новые функциональные возможности или исправить ошибку, то будет трудно определить, где внести изменение и при этом быть уверенным, что не будет непредсказуемых последствий в другом месте.

VIPER
В поиске лучшего способа спроектировать iOS приложение я наткнулся на Clean Architecture, как описал Uncle Bob. Clean Architecture делит логическую структуру приложения на различные уровни обязанностей. Это упрощает изолирование зависимости (например, ваша база данных) и тестирование взаимодействия на границах между уровнями.
VIPER является нашей реализацией Clean Architecture для iOS приложений.
Слово VIPER — бэкроним для View, Interactor, Presenter, Entity и Routing.
Структура VIPER
Если коротко у вас есть:
- Interactor, который содержит бизнес-логику, предусмотренную сценарием.
- Presenter, который содержит логику подготовки содержимого для отображения (полученного из Interactor) и для реакции на ввод данных пользователем (запрашивая новые данные от Interactor).
- View, которое отображает, что сообщил Presenter и передает ввод данных пользователем назад Presenter'у.
Первоначально мои коллеги именовали эту архитектуру как VIP архитектуру. В некотором смысле это нарушение прав, ведь это можно интерпретировать и как “Very Important Architecture”. Так как я не хочу, чтобы люди думали, что другие архитектурные решения не важны, я решил назвать ее VIPER и позже придумал, что E и R будут означать.
Это разделение также соответствует принципу Single Responsibility. Interactor ответствен за бизнес-аналитику, Presenter отвественныей за отображение, и View ответствено за визуальное предствление.
Ниже приведена схема различных компонентов и как они связаны между собой:

Interactor/Интерактор
Interactor является простым юз кейсом в приложении. Он содержит бизнес-логику для управления объектами (Entity), чтобы выполнить определенную задачу. Задача выполняется в Interactor'е, независимо от любого UI. Тот же Interactor можно использовать в iOS приложениях или консольных приложениях для Mac OS.
Поскольку Interactor является PONSO (Обычный NSObject), который прежде всего содержит логику, и его легко разработать при помощи TDD (Разработка через тестирование).
Entity/Сущность
Entity — это объекты, которыми управляет Interactor. Entity только управляет Interactor. Он никогда не передает сущности уровню представления (т.е. Presenter'у).
Data Store/Хранилище данных
Data Store (например, веб-сервис, база данных) отвечает за предоставление Entity в Interactor. Поскольку Interactor применяет свою бизнес-логику, он будет осуществлять выборку Entity из хранилища данных, управлять Entity и затем возвращать обновленные Entity назад в хранилище данных. Хранилище данных управляет персистентностью Entity. Entity не знают о хранилище данных, таким образом, они не знают, как сохраняться.
При использовании TDD (Разработка через тестирование) для разработки Interactor'a, возможно отключить производственное хранилище данных с помощью double/mock тестов. Не обращаясь к удаленному серверу (для веб-сервиса) или диска (для базы данных) позволяет вашим тестам быть повторяемыми и быстрыми.
Presenter/Презентатор
Presenter — это PONSO, который в основном состоит из логики, чтобы управлять UI. Он собирает входные данные от взаимодействия с пользователем, таким образом, он может отправлять запросы Interactor'у. Presenter также получает результаты Interactor'а и преобразовывать результаты в состояние, которое является наиболее эффективным для отображения на View.
Entity никогда не передаются из Interactor'а к Presenter'у. Вместо этого простые структуры данных, у которых нет поведения, передаются из Interactor'а к Presenter'у. Это препятствует любой ‘реальной работе’ в Presenter'е. Presenter может только подготовить данные для отображения на View.
View/Вид
View является пассивной. Оно ждет Presenter'а, чтобы передать содержания для выведения на экран; она никогда не запрашивает данные у Presenter'а. Методы, определенные для представления (например, LoginView для экрана входа в систему), должны позволить Presenter'у общаться на более высоком уровне абстракции, выраженной с точки зрения его содержимого, а не то, как это содержимое будет отображаться. Presenter не знает о существовании UILabel, UIButton, и т.д. Presenter только знает о содержании, которое он поддерживает и когда его нужно вывести на экран. Presenter нужно определять, как содержание выводиться на экран.
View — это абстрактный интерфейс, определенный в Objective-C с помощью протокола. UIViewController или один из его подклассов реализуют протокол View. Например, это может быть экран входа в систему:
@protocol LoginView <NSObject> - (void)setUserName:(NSString*)userName; - (void)setPassword:(NSString*)password; - (void)setLoginEnabled:(BOOL)enabled; @end @interface LoginViewController : UIViewController <LoginView> … @end
Wireframe/Каркас
Маршрутизация обрабатывает навигацию от одного экрана к другому, как определено в wireframes, созданных проектировщиком взаимодействия. Wireframe объект несет ответственность за маршрутизацию. Wireframe объект владеет объектами UIWindow, UINavigationController, и т.д. Он ответственен за создание Interactor, Presenter и View/ViewController и за настройки ViewController. Так как Presenter содержит логику, чтобы реагировать на ввод данных пользователем, Presenter знает, когда перейти на другой экран. Wireframe знает, как это сделать. Итак, Presenter — это пользователь Wireframa.
Пример
Вы можете найти приложение Counter, это простое приложение, которое демонстрирует использование Interactor, Presenter и View. В следующей статье будет более подробно рассказано о том, как это приложение было разработано. Дополнительные статьи проиллюстрируют использование хранилища данных и Wireframe.
Feedback
Таким образом, VIPER помогает нам быть более точными, что касается разделения проблем, разделяя большое количество кода одного класса на несколько меньших классов. За счет поддержания единственной ответственности в каждом классе это упростит разработку классов, используя TDD, которое позволяет нам более быстро реагировать на изменяющиеся требования и создавать лучшее программное обеспечение.

