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

Как работает ViewModel?

2.0 Middle🔥 111 комментариев
#Архитектура и паттерны

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

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

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

Как работает ViewModel в iOS (UIKit и SwiftUI)

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

Основные принципы работы ViewModel

1. Разделение ответственности

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

// Пример ViewModel для экрана профиля
class ProfileViewModel {
    // Модель данных
    private let user: User
    
    // Преобразованные данные для отображения
    var displayName: String {
        return "\(user.firstName) \(user.lastName)"
    }
    
    var formattedJoinDate: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd.MM.yyyy"
        return formatter.string(from: user.joinDate)
    }
    
    init(user: User) {
        self.user = user
    }
}

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

ViewModel содержит и управляет состоянием UI. Вместо того чтобы хранить состояние во ViewController (UIKit) или View (SwiftUI), мы выносим его в ViewModel:

class LoginViewModel: ObservableObject {
    // SwiftUI автоматически отслеживает изменения @Published свойств
    @Published var email: String = ""
    @Published var password: String = ""
    @Published var isLoading: Bool = false
    @Published var errorMessage: String?
    
    var isFormValid: Bool {
        return !email.isEmpty && !password.isEmpty && email.contains("@")
    }
}

3. Обработка бизнес-логики

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

class ProductListViewModel {
    private let apiService: APIServiceProtocol
    private var products: [Product] = []
    
    // Обновляемое состояние
    @Published var displayedProducts: [ProductDisplayItem] = []
    @Published var isLoading = false
    
    init(apiService: APIServiceProtocol = APIService()) {
        self.apiService = apiService
    }
    
    func loadProducts() {
        isLoading = true
        apiService.fetchProducts { [weak self] result in
            self?.isLoading = false
            switch result {
            case .success(let products):
                self?.products = products
                self?.mapToDisplayItems()
            case .failure(let error):
                // Обработка ошибки
                break
            }
        }
    }
    
    private func mapToDisplayItems() {
        displayedProducts = products.map { product in
            ProductDisplayItem(
                id: product.id,
                title: product.name,
                price: "$\(product.price)",
                isOnSale: product.discount > 0
            )
        }
    }
}

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

ViewModel обеспечивает двустороннюю привязку данных между UI и состоянием. В SwiftUI это достигается через @Published свойства и ObservableObject, а в UIKit — через замыкания, делегаты или реактивные фреймворки:

// SwiftUI пример привязки
struct ProductListView: View {
    @StateObject var viewModel = ProductListViewModel()
    
    var body: some View {
        List(viewModel.displayedProducts) { product in
            ProductRow(product: product)
        }
        .overlay {
            if viewModel.isLoading {
                ProgressView()
            }
        }
        .onAppear {
            viewModel.loadProducts()
        }
    }
}

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

Тестируемость

Поскольку ViewModel не зависит от UI-фреймворков, его легко тестировать:

class ProductListViewModelTests: XCTestCase {
    func testLoadingState() {
        // Arrange
        let mockService = MockAPIService()
        let viewModel = ProductListViewModel(apiService: mockService)
        
        // Act
        viewModel.loadProducts()
        
        // Assert
        XCTAssertTrue(viewModel.isLoading)
    }
}

Переиспользуемость

Один ViewModel может использоваться несколькими View (например, для iPhone и iPad версий).

Упрощение ViewController/View

View становится "тупым" компонентом, который только отображает данные и передает пользовательские действия.

Жизненный цикл ViewModel

  1. Инициализация — создается вместе с View или до него
  2. Конфигурация — настройка начального состояния
  3. Наблюдение — подписка на изменения данных
  4. Обработка действий — реакции на пользовательский ввод
  5. Очистка — отписка от наблюдаемых объектов (важно для предотвращения утечек памяти)

Особенности в разных iOS-фреймворках

  • SwiftUI: ViewModel обычно реализует ObservableObject, использует @Published свойства
  • UIKit: ViewModel может использовать делегаты, замыкания или Combine для обновления UI
  • Популярные подходы: Combine, RxSwift, обычные замыкания с обновлением UI через главную очередь

Проблемы и лучшие практики

  • Управление памятью: Всегда использовать [weak self] в замыканиях
  • Потокобезопасность: Обеспечивать доступ к состоянию с главного потока для UI-обновлений
  • Единая ответственность: Разделять большие ViewModel на более мелкие, специализированные
  • Инъекция зависимостей: Передавать сервисы через инициализатор для улучшения тестируемости

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