Архитектура VIPER в iOS разработке
Содержание:
Введение
VIPER (View, Interactor, Presenter, Entity, Router) — это одна из архитектур, используемых в разработке приложений для iOS. Она направлена на разделение обязанностей и улучшение тестируемости и расширяемости кода. VIPER является эволюцией популярных архитектур MVC (Model-View-Controller) и MVVM (Model-View-ViewModel), добавляя четкое разделение между слоями и строгую модульную структуру.
В этой статье мы подробно рассмотрим, что такое VIPER, какие задачи решают его компоненты, и как они взаимодействуют друг с другом. Мы также приведем примеры кода и визуальное представление структуры компонентов.
Основные компоненты VIPER
VIPER состоит из пяти основных компонентов:
- View: Отвечает за отображение данных и взаимодействие с пользователем.
- Interactor: Содержит бизнес-логику приложения.
- Presenter: Посредник между View и Interactor. Обрабатывает данные, полученные от Interactor, и передает их View.
- Entity: Модель данных.
- Router: Отвечает за навигацию между экранами.
1. View
Компонент View в VIPER отвечает за отображение данных и взаимодействие с пользователем. Он не содержит бизнес-логики и не управляет навигацией. Взаимодействие с другими компонентами осуществляется через Presenter.
import UIKit
protocol MyViewProtocol: AnyObject {
func showData(_ data: String)
}
class MyViewController: UIViewController, MyViewProtocol {
var presenter: MyPresenterProtocol!
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad()
}
func showData(_ data: String) {
// Отображение данных в интерфейсе
print(data)
}
}
2. Interactor
Interactor содержит бизнес-логику приложения. Он выполняет запросы к базам данных, сетевым сервисам и другим источникам данных. Результаты обработки передаются Presenter для дальнейшего отображения в View.
protocol MyInteractorProtocol: AnyObject {
func fetchData()
}
class MyInteractor: MyInteractorProtocol {
var presenter: MyPresenterProtocol!
func fetchData() {
// Выполнение бизнес-логики
let data = "Пример данных"
presenter.didFetchData(data)
}
}
3. Presenter
Presenter является посредником между View и Interactor. Он обрабатывает данные, полученные от Interactor, и подготавливает их для отображения в View.
protocol MyPresenterProtocol: AnyObject {
func viewDidLoad()
func didFetchData(_ data: String)
}
class MyPresenter: MyPresenterProtocol {
weak var view: MyViewProtocol?
var interactor: MyInteractorProtocol!
var router: MyRouterProtocol!
func viewDidLoad() {
interactor.fetchData()
}
func didFetchData(_ data: String) {
view?.showData(data)
}
}
4. Entity
Entity представляет собой модель данных. Она содержит данные, которые используются в приложении.
struct MyEntity {
let id: Int
let name: String
}
5. Router
Router отвечает за навигацию между экранами. Он создаёт и инициализирует компоненты модуля VIPER.
protocol MyRouterProtocol: AnyObject {
static func createModule() -> UIViewController
}
class MyRouter: MyRouterProtocol {
static func createModule() -> UIViewController {
let view = MyViewController()
let presenter = MyPresenter()
let interactor = MyInteractor()
let router = MyRouter()
view.presenter = presenter
presenter.view = view
presenter.interactor = interactor
presenter.router = router
interactor.presenter = presenter
return view
}
}
Взаимодействие компонентов
Компоненты VIPER взаимодействуют друг с другом через протоколы. Это обеспечивает слабую связность и позволяет легко заменять или модифицировать компоненты.
Жизненный цикл модуля
- View вызывает метод
viewDidLoad
у Presenter. - Presenter инициирует запрос данных у Interactor.
- Interactor выполняет бизнес-логику и передает данные обратно Presenter.
- Presenter форматирует данные и передает их View для отображения.
- Router управляет навигацией и созданием модулей.
Пример приложения на VIPER
Для лучшего понимания рассмотрим пример простого приложения на VIPER. Приложение будет отображать список пользователей и детальную информацию о каждом пользователе.
Структура проекта
- Modules
- UserList
- UserDetail
- Models
- User
Модель данных
struct User {
let id: Int
let name: String
let email: String
}
Модуль UserList
View
protocol UserListViewProtocol: AnyObject {
func showUsers(_ users: [User])
}
class UserListViewController: UIViewController, UserListViewProtocol {
var presenter: UserListPresenterProtocol!
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad()
}
func showUsers(_ users: [User]) {
// Отображение списка пользователей
}
}
Interactor
protocol UserListInteractorProtocol: AnyObject {
func fetchUsers()
}
class UserListInteractor: UserListInteractorProtocol {
var presenter: UserListPresenterProtocol!
func fetchUsers() {
// Выполнение бизнес-логики для получения списка пользователей
let users = [User(id: 1, name: "John Doe", email: "john@example.com")]
presenter.didFetchUsers(users)
}
}
Presenter
protocol UserListPresenterProtocol: AnyObject {
func viewDidLoad()
func didFetchUsers(_ users: [User])
func showUserDetail(for user: User)
}
class UserListPresenter: UserListPresenterProtocol {
weak var view: UserListViewProtocol?
var interactor: UserListInteractorProtocol!
var router: UserListRouterProtocol!
func viewDidLoad() {
interactor.fetchUsers()
}
func didFetchUsers(_ users: [User]) {
view?.showUsers(users)
}
func showUserDetail(for user: User) {
router.navigateToUserDetail(for: user)
}
}
Router
protocol UserListRouterProtocol: AnyObject {
static func createModule() -> UIViewController
func navigateToUserDetail(for user: User)
}
class UserListRouter: UserListRouterProtocol {
weak var viewController: UIViewController?
static func createModule() -> UIViewController {
let view = UserListViewController()
let presenter = UserListPresenter()
let interactor = UserListInteractor()
let router = UserListRouter()
view.presenter = presenter
presenter.view = view
presenter.interactor = interactor
presenter.router = router
interactor.presenter = presenter
router.viewController = view
return view
}
func navigateToUserDetail(for user: User) {
let userDetailViewController = UserDetailRouter.createModule(with: user)
viewController?.navigationController?.pushViewController(userDetailViewController, animated: true)
}
}
Модуль UserDetail
View
protocol UserDetailViewProtocol: AnyObject {
func showUserDetail(_ user: User)
}
class UserDetailViewController: UIViewController, UserDetailViewProtocol {
var presenter: UserDetailPresenterProtocol!
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad()
}
func showUserDetail(_ user: User) {
// Отображение детальной информации о пользователе
}
}
Interactor
protocol UserDetailInteractorProtocol: AnyObject {
func fetchUserDetail()
}
class UserDetailInteractor: UserDetailInteractorProtocol {
var presenter: UserDetailPresenterProtocol!
var user: User
init(user: User) {
self.user = user
}
func fetchUserDetail() {
// Возвращаем детальную информацию о пользователе
presenter.didFetchUserDetail(user)
}
}
Presenter
protocol UserDetailPresenterProtocol: AnyObject {
func viewDidLoad()
func didFetchUserDetail(_ user: User)
}
class UserDetailPresenter: UserDetailPresenterProtocol {
weak var view: UserDetailViewProtocol?
var interactor: UserDetailInteractorProtocol!
var router: UserDetailRouterProtocol!
func viewDidLoad() {
interactor.fetchUserDetail()
}
func didFetchUserDetail(_ user: User) {
view?.showUserDetail(user)
}
}
Router
protocol UserDetailRouterProtocol: AnyObject {
static func createModule(with user: User) -> UIViewController
}
class UserDetailRouter: UserDetailRouterProtocol {
static func createModule(with user: User) -> UIViewController {
let view = UserDetailViewController()
let presenter = UserDetailPresenter()
let interactor = UserDetailInteractor(user: user)
let router = UserDetailRouter()
view.presenter = presenter
presenter.view = view
presenter.interactor = interactor
presenter.router = router
interactor.presenter = presenter
return view
}
}
Визуализация структуры компонентов
Для наглядного представления структуры компонентов VIPER приведем схему взаимодействия между ними:
Заключение
Архитектура VIPER предоставляет четкое разделение обязанностей между компонентами, что делает код более модульным и тестируемым. Внедрение VIPER требует начальных усилий на организацию структуры проекта, но в дальнейшем это окупается улучшенной поддерживаемостью и масштабируемостью кода.
Использование VIPER особенно полезно в крупных проектах с комплексной бизнес-логикой и множеством экранов. Надеюсь, что приведенные примеры и объяснения помогут вам лучше понять, как применять эту архитектуру в своих проектах.