Что такое ViewModel в паттерне MVVM?

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

Ответ

ViewModel — это компонент архитектурного паттерна Model-View-ViewModel (MVVM), который выступает в роли посредника между Model (данные/бизнес-логика) и View (пользовательский интерфейс). Его основная задача — подготовить данные модели в удобной для отображения форме и обработать пользовательский ввод.

Роли и обязанности ViewModel:

  1. Абстракция данных: Преобразует сырые данные из Model (например, Date, Double) в отформатированные строки, которые может отобразить View ("Завтра, 15:30", "+25.5%").
  2. Предоставление состояния: Предоставляет View наблюдаемые свойства (например, через @Published в Combine) для отображения.
  3. Обработка действий: Содержит методы, которые вызываются View в ответ на действия пользователя (тапы, ввод текста).
  4. Навигация: Может управлять логикой перехода между экранами.

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

  • Независимость от UI: ViewModel не должна импортировать SwiftUI или UIKit и не содержит ссылок на UI-элементы. Это обеспечивает ее тестируемость и возможность повторного использования.
  • Связь через биндинг: View подписывается на изменения свойств ViewModel (через Combine/RxSwift/замыкания), обеспечивая реактивное обновление.

Пример ViewModel для экрана профиля в SwiftUI:

import Combine // Для @Published

// Model
struct User {
    let id: UUID
    let firstName: String
    let lastName: String
    let birthDate: Date
}

// ViewModel
class ProfileViewModel: ObservableObject {
    // Входные данные (обычно инжектируются)
    private let user: User
    private let service: NetworkService

    // Выходные данные для View
    @Published var fullName: String = ""
    @Published var age: String = ""
    @Published var isLoading: Bool = false

    init(user: User, service: NetworkService) {
        self.user = user
        self.service = service
        configureOutputs() // Преобразуем данные модели
    }

    private func configureOutputs() {
        // Преобразование данных Model для View
        fullName = "(user.firstName) (user.lastName)"
        let years = Calendar.current.dateComponents([.year], from: user.birthDate, to: Date()).year ?? 0
        age = "(years) лет"
    }

    // Обработка действий от View
    func refreshUserData() {
        isLoading = true
        service.fetchUser(by: user.id) { [weak self] result in
            DispatchQueue.main.async {
                self?.isLoading = false
                // Обработка результата и обновление @Published свойств
            }
        }
    }
}

// View
struct ProfileView: View {
    @StateObject var viewModel: ProfileViewModel // View владеет ViewModel

    var body: some View {
        VStack {
            if viewModel.isLoading {
                ProgressView()
            } else {
                Text(viewModel.fullName) // Подписывается на @Published fullName
                Text(viewModel.age)
                Button("Обновить") {
                    viewModel.refreshUserData() // Вызывает метод ViewModel
                }
            }
        }
    }
}

Преимущества использования ViewModel:

  • Тестируемость: Логику подготовки данных и обработки действий можно тестировать юнит-тестами без создания UI.
  • Разделение ответственности: View занимается только отрисовкой, ViewModel — логикой представления.
  • Упрощение View: View становится «тупым» (dumb) и декларативным.