← Назад к вопросам

Какая ответственность у ViewModel?

1.7 Middle🔥 241 комментариев
#Архитектура и паттерны#Язык Swift

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответственность ViewModel в архитектуре MVVM

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

Основные обязанности ViewModel:

1. Предоставление данных для View

ViewModel подготавливает данные из Model в формате, удобном для отображения в View. Он может форматировать, фильтровать, агрегировать или преобразовывать сырые данные.

class UserProfileViewModel {
    @Published var userName: String = ""
    @Published var formattedJoinDate: String = ""
    
    private let user: User
    
    init(user: User) {
        self.user = user
        configureDisplayData()
    }
    
    private func configureDisplayData() {
        userName = "\(user.firstName) \(user.lastName)"
        
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formattedJoinDate = "Joined: \(formatter.string(from: user.joinDate))"
    }
}

2. Обработка пользовательского ввода

ViewModel обрабатывает действия пользователя (тапы, жесты, ввод текста) и преобразует их в команды для Model или изменения состояния.

class LoginViewModel {
    @Published var email: String = ""
    @Published var password: String = ""
    @Published var isLoading: Bool = false
    @Published var errorMessage: String?
    
    private let authService: AuthenticationService
    
    func login() {
        isLoading = true
        authService.login(email: email, password: password) { [weak self] result in
            self?.isLoading = false
            switch result {
            case .success:
                // Обработка успешного входа
            case .failure(let error):
                self?.errorMessage = error.localizedDescription
            }
        }
    }
}

3. Управление состоянием View

ViewModel содержит и управляет состоянием, связанным с отображением данных:

  • Состояние загрузки (loading, success, error)
  • Видимость элементов интерфейса
  • Текст и значения элементов управления
  • Навигационные флаги

4. Реактивное программирование и привязка данных

В iOS ViewModel часто использует механизмы реактивного программирования для автоматического обновления View при изменении данных:

import Combine

class ShoppingCartViewModel: ObservableObject {
    @Published var items: [CartItem] = []
    @Published var totalPrice: Double = 0
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        $items
            .map { items in
                items.reduce(0) { $0 + $1.price * Double($1.quantity) }
            }
            .assign(to: \.totalPrice, on: self)
            .store(in: &cancellables)
    }
    
    func addItem(_ item: CartItem) {
        items.append(item)
    }
}

5. Координация бизнес-логики

ViewModel координирует взаимодействие между различными сервисами и репозиториями, не содержа при этом самой сложной бизнес-логики:

class WeatherViewModel {
    private let locationService: LocationService
    private let weatherService: WeatherService
    private let settingsRepository: SettingsRepository
    
    @Published var currentWeather: WeatherData?
    @Published var temperatureUnit: TemperatureUnit = .celsius
    
    init(locationService: LocationService, 
         weatherService: WeatherService,
         settingsRepository: SettingsRepository) {
        self.locationService = locationService
        self.weatherService = weatherService
        self.settingsRepository = settingsRepository
        
        loadSettings()
        fetchWeather()
    }
    
    private func loadSettings() {
        temperatureUnit = settingsRepository.getTemperatureUnit()
    }
}

6. Подготовка данных для навигации

ViewModel может подготавливать данные, необходимые для перехода к другим экранам:

class ProductListViewModel {
    @Published var products: [Product] = []
    
    func productDetailsViewModel(for productId: String) -> ProductDetailsViewModel? {
        guard let product = products.first(where: { $0.id == productId }) else {
            return nil
        }
        return ProductDetailsViewModel(product: product)
    }
}

Что ViewModel НЕ должен делать:

  1. Прямая работа с UI элементами — ViewModel не должен содержать ссылки на UIView/UIViewController
  2. Манипуляции с DOM/UIKit элементами — это ответственность View
  3. Сложная бизнес-логика — она должна находиться в Model или отдельных сервисах
  4. Прямой доступ к базе данных или сети — доступ через репозитории/сервисы
  5. Хранение тяжелых объектов — во избежание утечек памяти

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

  • Тестируемость: ViewModel можно тестировать без UI
  • Переиспользование: одну ViewModel можно использовать с разными View
  • Инкапсуляция: логика представления изолирована от UI кода
  • Поддержка жизненного цикла: правильная обработка конфигурационных изменений на iOS
  • Отсоединение зависимостей: View не зависит от конкретной реализации Model

Правильное разделение ответственности между View, ViewModel и Model — ключ к созданию масштабируемых и поддерживаемых iOS-приложений, особенно при работе с SwiftUI или комбинировании с реактивными подходами.