Как реализовать архитектуру MVVM в UIKit?

Ответ

MVVM (Model-View-ViewModel) в UIKit разделяет ответственность:

  • Model — данные и бизнес-логика.
  • ViewUIViewController и UIView, отвечающие за отображение.
  • ViewModel — преобразует Model в данные, готовые для отображения View, и содержит презентационную логику.

Базовая реализация:

// MARK: - Model
struct User {
    let name: String
    let age: Int
}

// MARK: - ViewModel
class UserViewModel {
    private let user: User

    // Преобразование данных для View
    var displayName: String { "User: (user.name)" }
    var displayAge: String { "Age: (user.age)" }

    init(user: User) {
        self.user = user
    }
}

// MARK: - View (ViewController)
class UserViewController: UIViewController {
    @IBOutlet private weak var nameLabel: UILabel!
    @IBOutlet private weak var ageLabel: UILabel!

    var viewModel: UserViewModel! {
        didSet { updateView() }
    }

    private func updateView() {
        nameLabel.text = viewModel.displayName
        ageLabel.text = viewModel.displayAge
    }
}

Ключевые принципы:

  1. ViewModel не имеет ссылок на UIKit. Она независима от слоя представления.
  2. View (ViewController) становится "глупым". Он только отображает данные из ViewModel и передаёт ей пользовательские действия.
  3. Связывание (Binding): Для реактивного обновления View при изменении ViewModel используйте:
    • Closures или делегаты для простых случаев.
    • Combine фреймворк (iOS 13+) для декларативного биндинга.
    • RxSwift или ReactiveCocoa в сторонних проектах.

Преимущества: Упрощается тестирование (ViewModel тестируется без UIKit), уменьшается связанность кода, ViewController становится менее массивным.

Ответ 18+ 🔞

А, MVVM в UIKit, да? Ну это ж классика, блядь! Как эти ваши деревянные матрешки, только в коде, ёпта. Сейчас разложу по полочкам, как нормальный человек, а не как эти заумные учебники, от которых глаза на лоб лезут.

Смотри, вся фишка в том, чтобы растащить ответственность, а то ViewController у нас обычно — такая свалка, что мама не горюй! Он и данные качает, и интерфейсом вертит, и бизнес-логику пытается в себя впихнуть. Пиздец, а не класс. MVVM приходит и говорит: «Так, пацаны, разойдись! Каждый занимается своим делом».

Кто на что учился:

  • Model (Модель) — это, по сути, данные. Голые, как сосиски из холодильника. Имя, возраст, флаги какие-нибудь. Никакой тебе красоты, просто факты.
  • View (Вьюха) — это наш UIViewController со всеми его UILabel, UIButton. Его задача — тыкать пальцем в экран и показывать, что ему сказали. Больше ни-ху-я. Идеальный солдат.
  • ViewModel (ВьюМодель) — вот тут вся магия, блядь! Это такой переводчик-секретарь. Берёт голые данные из Model, делает из них красивую строку для лейбла, решает, какую кнопку скрыть, а какую показать. Вся презентационная логика — тут.

Вот, смотри, как это выглядит в коде, без этих твоих заумных маркеров:

// Это наша сосиска-модель. Данные и всё.
struct User {
    let name: String
    let age: Int
}

// А это — наш главный затейник, ViewModel.
class UserViewModel {
    private let user: User // Держит модельку при себе.

    // Он уже приготовил данные, разжевал и в рот положил.
    var displayName: String { "Юзер: (user.name)" }
    var displayAge: String { "Возраст: (user.age)" }

    init(user: User) {
        self.user = user
    }
}

// Ну и сам ViewController, который стал внезапно очень простым.
class UserViewController: UIViewController {
    @IBOutlet private weak var nameLabel: UILabel!
    @IBOutlet private weak var ageLabel: UILabel!

    // Приходит ViewModel — и сразу обновляемся!
    var viewModel: UserViewModel! {
        didSet { updateView() }
    }

    private func updateView() {
        nameLabel.text = viewModel.displayName // Берём готовенькое!
        ageLabel.text = viewModel.displayAge   // И тут берём!
    }
}

А теперь главные правила, которые нарушать — себя не уважать:

  1. ViewModel НЕ ДОЛЖНА ЗНАТЬ ПРО UIKit. Вообще. Никаких UILabel, UIViewController. Она живёт в своём прекрасном мире строк, чисел и булевых флагов. Если ты импортируешь UIKit в ViewModel — ты проебалася.
  2. ViewController превращается в тупого исполнителя. Его удел — взять данные из ViewModel и запихнуть их в аутлеты. Или отловить нажатие кнопки и сказать об этом ViewModel. Всё! Никаких if-else на три экрана про то, скрывать ли кнопку.
  3. Связывание (Binding). Вот это, блядь, самый сок. Чтобы не тыкать кнопкой updateView() каждый раз, используют биндинг. ViewModel меняется — View автоматически обновляется. Как это сделать?
    • На коленке: closures или делегаты. Для мелких проектов сойдёт.
    • По-взрослому (iOS 13+): фреймворк Combine. Это охуенно, но надо голову настроить.
    • Из стороннего: RxSwift или ReactiveCocoa. Мощно, но это отдельная религия.

И что в сухом остатке, спросишь ты? А то, что тестировать эту хуйню становится в разы проще. ViewModel можно гонять юнит-тестами, потому что в ней нет никакого UIKit. ViewController, который теперь тупой как пробка, можно не тестировать вообще, либо тестировать через UI-тесты. И главное — в коде порядок, а не свалка. ViewController перестаёт быть божеством на 1000 строк, и жить становится легче.