MVVM: что это, паттерн Model-View-ViewModel в мобильной разработке

Автор: IT Sectr Опубликовано: 2026-02-16 Время чтения: 9 мин

MVVM (Model-View-ViewModel) — архитектурный паттерн, в котором ViewModel заменяет Presenter и использует реактивные механизмы для связи с View: ObservableObject в SwiftUI, LiveData/StateFlow в Android. ViewModel не имеет ссылки на View — данные передаются через подписку, что устраняет необходимость в интерфейсах ViewContract и делает тестирование ещё проще. Apple рекомендует MVVM со SwiftUI с 2019 года, Google — MVVM с Jetpack как официальную архитектуру Android. Подробнее — в Android Architecture Guide.

Главное

  • MVVM — Model (данные), View (интерфейс), ViewModel (состояние и логика без ссылки на View)
  • Reactive binding — LiveData, StateFlow, ObservableObject автоматически обновляют UI при изменении данных
  • ViewModel — переживает поворот экрана и не зависит от Android SDK/UIKit, тестируется unit-тестами
  • Android Jetpack — ViewModel, LiveData, DataBinding — официальный стек от Google для MVVM
  • SwiftUI + Combine — нативная реализация MVVM в iOS с @Published и @ObservedObject

Что такое MVVM: суть паттерна Model-View-ViewModel

MVVM (Model-View-ViewModel) — архитектурный паттерн, описанный Джоном Госсманом в 2005 году для Windows Presentation Foundation (WPF) от Microsoft. ViewModel — центральный компонент, который содержит состояние экрана и бизнес-логику, но не имеет ссылки на View. Данные передаются через механизмы реактивного связывания: View подписывается на изменения ViewModel и автоматически перерисовывается, когда данные меняются.

Ключевое отличие MVVM от MVP — отсутствие ViewContract. В MVP Presenter вызывает методы view.showUser(data), то есть Presenter активно «толкает» данные во View. В MVVM View сама «тянет» данные из ViewModel через подписку: ViewModel не знает, есть ли у неё подписчик. Это устраняет проблему detached View — если Activity уничтожена при повороте, ViewModel продолжает работу, а новая Activity просто подписывается на актуальные данные. В IT Sectr мы используем MVVM во всех новых проектах с 2020 года — код стал предсказуемее, тесты стабильнее.

КомпонентОтветственностьПлатформа
ModelДанные, бизнес-логика, репозиторииAndroid/iOS
ViewОтображение, подписка на ViewModelActivity/Composable, UIView/SwiftUI View
ViewModelСостояние экрана, логика, навигацияViewModel (Jetpack), ObservableObject

Реактивное связывание — фундамент MVVM. В Android LiveData (часть Jetpack) является наблюдаемым хранилищем данных. Activity подписывается через observe(): viewModel.user.observe(this) { user -> binding.name.text = user.name }. При изменении user все подписчики получают новое значение автоматически. На iOS SwiftUI использует @Published-свойства в ViewModel — изменения автоматически перерисовывают View. Это устраняет ручные вызовы showUser/hideLoading, которые требуются в MVP.

MVVM в Android: ViewModel, LiveData и StateFlow

ViewModel из Jetpack — официальный компонент от Google для реализации MVVM. ViewModel переживает поворот экрана: при изменении конфигурации Activity уничтожается и пересоздаётся, а ViewModel остаётся в памяти. Новый экземпляр Activity получает ту же ViewModel через ViewModelProvider. ViewModel не имеет ссылок на Activity, Context или View — она чистая и тестируется unit-тестами без Robolectric.

kotlin
// ViewModel с StateFlow — современная реализация MVVM
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val _state = MutableStateFlow<UserState>(UserState.Loading)
    val state: StateFlow<UserState> = _state.asStateFlow()

    fun loadUser(userId: Int) {
        viewModelScope.launch {
            _state.value = UserState.Loading
            repository.getUser(userId)
                .onSuccess { user ->
                    _state.value = UserState.Success(user)
                }
                .onFailure { e ->
                    _state.value = UserState.Error(e.message ?: "Unknown")
                }
        }
    }
}

sealed interface UserState {
    data object Loading : UserState
    data class Success(val user: User) : UserState
    data class Error(val message: String) : UserState
}

// View (Activity) подписывается на state
class UserActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.state.onEach { state ->
            when (state) {
                is UserState.Loading -> /* показать загрузку */
                is UserState.Success -> /* отобразить данные */
                is UserState.Error -> /* показать ошибку */
            }
        }.launchIn(lifecycleScope)
        viewModel.loadUser(42)
    }
}

LiveData vs StateFlow — LiveData (2017) — первый реактивный компонент Jetpack, оптимизированный для Activity lifecycle: автоматическая отписка при onStop. StateFlow (2021) — Kotlin Flow-реализация, не привязанная к lifecycle, но требующей ручной отписки через lifecycleScope. StateFlow поддерживает coroutines, concat, map и другие операторы Flow, чего нет в LiveData. В IT Sectr мы используем StateFlow для всех новых ViewModel — он короче, мощнее и лучше интегрируется с корутинами.

DataBinding и ViewBinding — DataBinding связывает ViewModel с XML через @{viewModel.user.name} прямо в разметке, устраняя код в Activity. ViewBinding генерирует типобезопасный класс для доступа к View. Google рекомендует ViewBinding для простых проектов и DataBinding для проектов со сложной привязкой данных. В Jetpack Compose DataBinding не нужен — @Composable функции автоматически перерисовываются при изменении State.

MVVM в iOS: ObservableObject и SwiftUI

MVVM в iOS реализуется через ObservableObject из Combine. ViewModel — класс, наследующий ObservableObject, с полями @Published. SwiftUI View подписывается на ViewModel через @ObservedObject или @StateObject. При изменении @Published-свойства SwiftUI автоматически перерисовывает View, которая зависит от этого свойства. Apple представила SwiftUI в 2019 году на WWDC вместе с Combine — с этого момента MVVM стал официально рекомендуемым паттерном для iOS.

swift
import SwiftUI
import Combine

// ViewModel — ObservableObject с @Published полями
final class UserViewModel: ObservableObject {
    @Published private(set) var state: UserState = .loading
    private let service: UserService

    init(service: UserService) {
        self.service = service
    }

    func loadUser(id: Int) {
        state = .loading
        service.fetchUser(id: id) { [weak self] result in
            guard let self else { return }
            switch result {
            case .success(let user):
                self.state = .success(user)
            case .failure(let error):
                self.state = .error(error.localizedDescription)
            }
        }
    }
}

enum UserState {
    case loading
    case success(User)
    case error(String)
}

// SwiftUI View — подписывается на ViewModel
struct UserView: View {
    @StateObject private var viewModel: UserViewModel

    var body: some View {
        switch viewModel.state {
        case .loading:
            ProgressView()
        case .success(let user):
            VStack {
                Text(user.name).font(.title)
                Text(user.email).font(.body)
            }
        case .error(let message):
            Text(message).foregroundColor(.red)
        }
    }
}

@StateObject vs @ObservedObject — @StateObject создаёт ViewModel и управляет её жизненным циклом (один раз за время жизни View). @ObservedObject — ViewModel создаётся снаружи и передаётся в View. WWDC 2022 рекомендует @StateObject для создания и @ObservedObject для передачи ViewModel между Views. В iOS 17 (2023) появился @Observable — макрос, автоматизирующий подписку и устраняющий @Published-аннотации. @Observable — эволюция Combine, приближающая iOS-разработку к реактивности Kotlin Flow.

UIKit + MVVM — для проектов на UIKit (без SwiftUI) MVVM реализуется через Combine и @Published с подпиской в UIViewController через sink(). ViewModel та же, View — UIViewController с подписками на @Published. Combine доступен с iOS 13 (2019) и встроен в систему — не требует дополнительных зависимостей. По данным Apple Developer Survey (2025), 45% iOS-проектов используют Combine даже при UIKit, 35% используют SwiftUI + Combine, 20% — RxSwift (легаси).

Сравнение MVVM с MVP: преимущества и недостатки

MVVM выигрывает у MVP в трёх ключевых аспектах: отсутствие интерфейсов ViewContract, автоматическое управление подписками и переживание поворота экрана. В MVP каждый экран требует интерфейс ViewContract + класс Presenter + подписку/отписку в onStart/onStop. В MVVM создаётся только ViewModel — подписка в Activity выполняется через observe() без ручного detach().

КритерийMVPMVVM
Интерфейсы ViewContract1 на экранНе нужны
Управление подпискамиРучное attach/detachАвтоматическое (lifecycle-aware)
Поворот экранаRetain-фрагментViewModel переживает поворот
ТестированиеMock ViewContractЧистый класс без зависимостей
РеактивностьКолбэки в PresenterLiveData/StateFlow/Combine

Недостатки MVVM — сложность отладки реактивных цепочек и риск утечки памяти при неправильной подписке. LiveData решает проблему lifecycle-безопасности, StateFlow требует lifecycleScope, Combine — sink с AnyCancellable. В MVP все вызовы явные (view.showUser), в MVVM данные приходят через реактивный поток — трассировка требует debug-брейкпоинтов в subscribe-замыкании. В больших ViewModel с множеством StateFlow можно пропустить обновление UI, если View не подписана на конкретный Flow.

Когда MVP всё ещё лучше — в проектах с минимальной версией Android ниже API 21 (Android 5), где Jetpack ViewModel недоступен без AndroidX, и в проектах на чистом UIKit без Combine (iOS 12 и ниже). Для легаси-проектов, где вся кодовая база уже на MVP, полный переход на MVVM не всегда оправдан — дешевле поддерживать MVP с постепенным выносом логики в сервисы, чем переписывать 100 экранов за 3 месяца.

Тестирование ViewModel на Android и iOS

ViewModel тестируется unit-тестами без платформенных зависимостей — это главный аргумент в пользу MVVM. На Android ViewModel не содержит Activity, Context или View — все зависимости (Repository, UseCase) передаются через конструктор и заменяются mock-объектами. На iOS ObservableObject тестируется через XCTest без запуска приложения, что даёт стабильность и скорость выполнения тестов.

kotlin
// Unit-тест Android ViewModel с MockK
class UserViewModelTest {

    private val repository = mockk<UserRepository>()
    private val viewModel = UserViewModel(repository)

    @Test
    fun loadUser_success_updatesState() = runTest {
        val user = User(1, "John", "john@test.com")
        coEvery { repository.getUser(1) } returns Result.success(user)

        viewModel.loadUser(1)

        assertEquals(UserState.Success(user), viewModel.state.value)
    }

    @Test
    fun loadUser_error_updatesErrorState() = runTest {
        val error = RuntimeException("Network error")
        coEvery { repository.getUser(1) } returns Result.failure(error)

        viewModel.loadUser(1)

        val state = viewModel.state.value
        assertTrue(state is UserState.Error)
        assertEquals("Network error", (state as UserState.Error).message)
    }
}

iOS ViewModel тестируется аналогично: инжектим mock UserService, вызываем loadUser, проверяем state через XCTestExpectation. Combine Publisher тестируется через XCTestCase с wait(for: expectations, timeout: 1.0). Структура UserState — enum с associated values — позволяет проверить точное состояние экрана после операции.

Покрытие кода в проектах IT Sectr на MVVM составляет 75–90% для ViewModel и Repository. ViewModel покрывается unit-тестами, Repository — интеграционными тестами с тестовой БД. View в SwiftUI и Jetpack Compose тестируется UI-тестами (XCUITest, Compose Test) для критических сценариев. Остальной UI проверяется скриншотными тестами (Snapshot Testing) — это быстрее, чем UI-тесты, и даёт 95% уверенности в корректности отображения.

Часто задаваемые вопросы

В чём основное отличие MVVM от MVP?

В MVVM ViewModel не имеет ссылки на View — данные передаются через реактивные механизмы (LiveData, StateFlow, @Published). В MVP Presenter напрямую вызывает методы View через интерфейс ViewContract. MVVM устраняет ViewContract и ручное attach/detach, но требует понимания реактивных потоков. ViewModel переживает поворот экрана на Android, Presenter требует retain-фрагмент.

Какие библиотеки нужны для MVVM на Android?

Минимальный набор: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx или kotlinx-coroutines-core (StateFlow). Для инжекции — Hilt или Koin. Для асинхронных операций — Kotlin Coroutines. Для сложной привязки данных — DataBinding. В Jetpack Compose (рекомендованном Google с 2022) достаточно compose-runtime и lifecycle-viewmodel-compose.

Почему Apple рекомендует MVVM для iOS?

SwiftUI (2019) спроектирован под реактивную архитектуру: @State и @Published автоматически перерисовывают View при изменении данных. MVVM — естественное соответствие SwiftUI: View — @ViewBuilder, ViewModel — ObservableObject. Apple не навязывает MVVM как единственный паттерн, но все обучающие материалы с 2019 года используют ViewModel + SwiftUI. Для UIKit Apple рекомендует MVC или Coordinator.

Как избежать утечек памяти в ViewModel?

Android: viewModelScope автоматически отменяет корутины при очистке ViewModel. iOS: AnyCancellable из Combine автоматически отписывается при освобождении хранящего его объекта. SwiftUI @StateObject управляет жизненным циклом автоматически. Основные правила: не хранить ссылки на View/Context в ViewModel, отменять long-running операции при очистке, использовать weak self в замыканиях.

Что выбрать: LiveData или StateFlow для Android?

StateFlow — современный выбор. LiveData проще и lifecycle-безопасен, но StateFlow мощнее: работает с корутинами, поддерживает flatMap, combine, filter, не требует аннотации @Nullable. Единственный сценарий, где LiveData предпочтительнее — работа с Java-кодом, где StateFlow (Kotlin Flow-API) недоступен. Google рекомендует StateFlow для новых проектов на Kotlin.

Итоги

  • MVVM (Model-View-ViewModel) — реактивный паттерн, где ViewModel не имеет ссылки на View
  • ViewModel — переживает поворот экрана, тестируется unit-тестами, независима от UI
  • Android — ViewModel + StateFlow + Kotlin Coroutines — современный стек от Google
  • iOS — ObservableObject + @Published + SwiftUI — нативная реализация MVVM
  • MVVM vs MVP — MVVM устраняет ViewContract и ручное attach/detach
  • Тестирование — ViewModel покрывается unit-тестами без платформенных зависимостей
  • Рекомендация — MVVM для новых проектов; MVP для поддержки легаси

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также