Ответ
ViewModel — это компонент архитектурного паттерна Model-View-ViewModel (MVVM), который выступает в роли посредника между Model (данные/бизнес-логика) и View (пользовательский интерфейс). Его основная задача — подготовить данные модели в удобной для отображения форме и обработать пользовательский ввод.
Роли и обязанности ViewModel:
- Абстракция данных: Преобразует сырые данные из Model (например,
Date,Double) в отформатированные строки, которые может отобразить View ("Завтра, 15:30","+25.5%"). - Предоставление состояния: Предоставляет View наблюдаемые свойства (например, через
@Publishedв Combine) для отображения. - Обработка действий: Содержит методы, которые вызываются View в ответ на действия пользователя (тапы, ввод текста).
- Навигация: Может управлять логикой перехода между экранами.
Ключевые принципы:
- Независимость от 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) и декларативным.