Опиши пример реализации архитектуры VIPER на iOS.

«Опиши пример реализации архитектуры VIPER на iOS.» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

VIPER — это архитектурный паттерн, разделяющий ответственность на пять компонентов: View, Interactor, Presenter, Entity, Router. Он обеспечивает высокую тестируемость и разделение модулей.

Пример: Модуль входа в систему (Login)

  1. Entity (Модель данных)

    struct User {
    let email: String
    }
    // Entity — это простые структуры данных, без логики.
  2. Interactor (Бизнес-логика)

    
    protocol LoginInteractorInputProtocol: AnyObject {
    func loginUser(email: String, password: String)
    }

protocol LoginInteractorOutputProtocol: AnyObject { func loginDidSucceed(user: User) func loginDidFail(error: Error) }

final class LoginInteractor: LoginInteractorInputProtocol { weak var presenter: LoginInteractorOutputProtocol? private let authService: AuthServiceProtocol // Зависимость для работы с сетью/БД

func loginUser(email: String, password: String) {
    authService.login(email: email, password: password) { [weak self] result in
        switch result {
        case .success(let user):
            self?.presenter?.loginDidSucceed(user: user)
        case .failure(let error):
            self?.presenter?.loginDidFail(error: error)
        }
    }
}

}


3.  **Presenter (Логика представления)**
```swift
protocol LoginPresenterProtocol: AnyObject {
    func viewDidLoad()
    func didTapLoginButton(email: String?, password: String?)
}

final class LoginPresenter: LoginPresenterProtocol {
    weak var view: LoginViewProtocol?
    var interactor: LoginInteractorInputProtocol?
    var router: LoginRouterProtocol?

    func didTapLoginButton(email: String?, password: String?) {
        guard let email = email, let password = password, !email.isEmpty, !password.isEmpty else {
            view?.showValidationError()
            return
        }
        view?.showLoadingIndicator(true)
        interactor?.loginUser(email: email, password: password)
    }
}

// Presenter также реализует протокол вывода Interactor'а
extension LoginPresenter: LoginInteractorOutputProtocol {
    func loginDidSucceed(user: User) {
        view?.showLoadingIndicator(false)
        router?.routeToHomeScreen(with: user)
    }

    func loginDidFail(error: Error) {
        view?.showLoadingIndicator(false)
        view?.showError(message: error.localizedDescription)
    }
}
  1. View (Отображение)
    
    protocol LoginViewProtocol: AnyObject {
    func showLoadingIndicator(_ show: Bool)
    func showError(message: String)
    func showValidationError()
    }

final class LoginViewController: UIViewController, LoginViewProtocol { var presenter: LoginPresenterProtocol! @IBOutlet private weak var emailTextField: UITextField!

override func viewDidLoad() {
    super.viewDidLoad()
    presenter.viewDidLoad()
}

@IBAction private func loginButtonTapped() {
    presenter.didTapLoginButton(email: emailTextField.text, password: "...")
}

// MARK: - LoginViewProtocol
func showError(message: String) {
    let alert = UIAlertController(title: "Ошибка", message: message, preferredStyle: .alert)
    present(alert, animated: true)
}
// ... остальные методы протокола

}


5.  **Router (Навигация)**
```swift
protocol LoginRouterProtocol: AnyObject {
    func routeToHomeScreen(with user: User)
}

final class LoginRouter: LoginRouterProtocol {
    weak var viewController: UIViewController?

    func routeToHomeScreen(with user: User) {
        let homeVC = HomeModuleBuilder.build(with: user)
        viewController?.navigationController?.pushViewController(homeVC, animated: true)
    }
}

Сборка модуля (Assembly/Builder):

enum LoginModuleBuilder {
    static func build() -> UIViewController {
        let view = LoginViewController()
        let presenter = LoginPresenter()
        let interactor = LoginInteractor()
        let router = LoginRouter()

        view.presenter = presenter
        presenter.view = view
        presenter.interactor = interactor
        presenter.router = router
        interactor.presenter = presenter
        router.viewController = view

        return view
    }
}

Преимущества VIPER: Четкое разделение ответственности, высокая тестируемость каждого компонента изолированно, удобство поддержки больших проектов.