Архитектурные паттерны в Swift: MVC, MVVM и VIPER с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Архитектурные паттерны в Swift: от хаоса к порядку

Каждый iOS-разработчик рано или поздно сталкивается с вопросом: "Как организовать код, чтобы проект не превратился в лапшу через полгода?" Ответ прост — архитектурные паттерны. В Swift, как и в любом другом языке, выбор архитектуры определяет, насколько удобно будет поддерживать, тестировать и расширять приложение. Сегодня мы разберем три самых популярных подхода: MVC, MVVM и VIPER. Никакой сухой теории — только живой код и практические советы.

Если вы пишете на Swift, но до сих пор используете "все в одном файле" — эта статья для вас. Поехали!

MVC (Model-View-Controller) — классика жанра

MVC — это паттерн, который Apple закладывает в основу UIKit. Казалось бы, все просто: Model отвечает за данные, View — за отображение, Controller — связывает их. Но на практике MVC в iOS часто превращается в Massive View Controller — контроллер, который знает все, делает все и весит тысячи строк.

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

// Model
struct User {
    let name: String
    let email: String
}

// View (не UI-код, а протокол для обновления)
protocol UserListView: AnyObject {
    func showUsers(_ users: [User])
    func showError(_ error: Error)
}

// Controller
class UserListViewController: UIViewController, UserListView {
    
    @IBOutlet weak var tableView: UITableView!
    private var users: [User] = []
    private let dataService = DataService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        loadUsers()
    }
    
    func loadUsers() {
        dataService.fetchUsers { [weak self] result in
            switch result {
            case .success(let users):
                self?.showUsers(users)
            case .failure(let error):
                self?.showError(error)
            }
        }
    }
    
    func showUsers(_ users: [User]) {
        self.users = users
        tableView.reloadData()
    }
    
    func showError(_ error: Error) {
        let alert = UIAlertController(title: "Ошибка", message: error.localizedDescription, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

extension UserListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
        let user = users[indexPath.row]
        cell.textLabel?.text = user.name
        cell.detailTextLabel?.text = user.email
        return cell
    }
}

Плюсы MVC:

  • Простота и понятность — каждый новый разработчик быстро вникает.
  • Нативная поддержка Apple — не нужно подключать сторонние библиотеки.
  • Быстрый старт для небольших проектов.

Минусы MVC:

  • Контроллеры становятся "толстыми" и трудно тестируемыми.
  • Сложно переиспользовать логику между разными экранами.
  • При росте проекта код превращается в спагетти.

Вывод: MVC хорош для простых приложений или прототипов. Если вы знаете, что проект будет расти — смотрите в сторону MVVM или VIPER.

MVVM (Model-View-ViewModel) — современный стандарт

MVVM решает главную проблему MVC — разгружает контроллер. Вся бизнес-логика и подготовка данных для отображения выносится в ViewModel. View (контроллер) только подписывается на изменения и обновляет интерфейс. Это делает код тестируемым и чистым.

Вот тот же пример, но на MVVM с использованием Combine (или можно заменить на простые замыкания).

// Model
struct User {
    let name: String
    let email: String
}

// ViewModel
class UserListViewModel {
    
    @Published var users: [User] = []
    @Published var errorMessage: String?
    
    private let dataService = DataService()
    
    func loadUsers() {
        dataService.fetchUsers { [weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success(let users):
                    self?.users = users
                case .failure(let error):
                    self?.errorMessage = error.localizedDescription
                }
            }
        }
    }
}

// View (ViewController)
class UserListViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var errorLabel: UILabel!
    
    private var viewModel = UserListViewModel()
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        bindViewModel()
        viewModel.loadUsers()
    }
    
    private func bindViewModel() {
        viewModel.$users
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.tableView.reloadData()
            }
            .store(in: &cancellables)
        
        viewModel.$errorMessage
            .receive(on: DispatchQueue.main)
            .sink { [weak self] message in
                self?.errorLabel.text = message
                self?.errorLabel.isHidden = (message == nil)
            }
            .store(in: &cancellables)
    }
}

extension UserListViewController: UITableViewDataSource {
    // ... аналогично MVC
}

Плюсы MVVM:

  • Чистое разделение ответственности — ViewModel не знает о UIKit.
  • Легко тестировать — достаточно проверить ViewModel.
  • Отлично сочетается с реактивными фреймворками (Combine, RxSwift).

Минусы MVVM:

  • Больше кода и файлов, чем в MVC.
  • Требуется понимание реактивного программирования.
  • При неправильной реализации ViewModel тоже может стать "толстой".

MVVM — это золотая середина. Я рекомендую его для большинства коммерческих проектов. Он дает баланс между простотой и масштабируемостью.

VIPER — архитектура для энтерпрайза

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

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

// Entity — модель данных (чистая Swift структура)
struct UserEntity {
    let name: String
    let email: String
}

// Interactor — бизнес-логика и работа с данными
protocol UserListInteractorProtocol {
    func fetchUsers()
}

class UserListInteractor: UserListInteractorProtocol {
    weak var presenter: UserListPresenterProtocol?
    private let dataService = DataService()
    
    func fetchUsers() {
        dataService.fetchUsers { [weak self] result in
            switch result {
            case .success(let users):
                self?.presenter?.usersFetched(users.map { UserEntity(name: $0.name, email: $0.email) })
            case .failure(let error):
                self?.presenter?.usersFetchFailed(error.localizedDescription)
            }
        }
    }
}

// Presenter — подготовка данных для View и обработ                

Блоги

Рекомендаций по книгам