MVVM:什么是Model-View-ViewModel模式在移动开发中的应用

作者: IT Sectr 发布日期: 2026-02-16 阅读时间: 9 分钟

MVVM(Model-View-ViewModel)——一种架构模式,其中ViewModel替代Presenter并使用反应式机制与View通信:SwiftUI中的ObservableObject,Android中的LiveData/StateFlow。ViewModel没有对View的引用——数据通过订阅传递,这消除了对ViewContract接口的需求,并使测试更加简单。Apple自2019年起推荐MVVM与SwiftUI结合使用,Google则推荐MVVM与Jetpack作为Android的官方架构。更多信息请参阅Android Architecture Guide

要点

  • MVVM——Model(数据)、View(界面)、ViewModel(状态和逻辑,不引用View)
  • Reactive binding——LiveData、StateFlow、ObservableObject在数据变化时自动更新UI
  • ViewModel——在屏幕旋转后依然存活,不依赖于Android SDK/UIKit,可通过单元测试进行测试
  • Android Jetpack——ViewModel、LiveData、DataBinding——Google用于MVVM的官方技术栈
  • SwiftUI + Combine——iOS中MVVM的原生实现,使用@Published和@ObservedObject

什么是MVVM:Model-View-ViewModel模式的本质

MVVM(Model-View-ViewModel)——一种由John Gossman于2005年为Microsoft的Windows Presentation Foundation(WPF)描述的架构模式。ViewModel是包含屏幕状态和业务逻辑的核心组件,但不引用View。数据通过反应式绑定机制传递:View订阅ViewModel的变化,并在数据变化时自动重新渲染。

MVVM与MVP的关键区别——没有ViewContract。在MVP中,Presenter调用view.showUser(data)等方法,即Presenter主动将数据“推送”到View。在MVVM中,View通过订阅从ViewModel“拉取”数据:ViewModel不知道是否有订阅者。这消除了分离View的问题——如果Activity在旋转时被销毁,ViewModel继续工作,新的Activity只需订阅当前数据。从2020年开始,我们在IT Sectr的所有新项目中使用MVVM——代码变得更可预测,测试更稳定。

组件职责平台
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使用ViewModel中的@Published属性——变化会自动重新渲染View。这消除了MVP中所需的手动showUser/hideLoading调用。

Android中的MVVM:ViewModel、LiveData和StateFlow

来自Jetpack的ViewModel——Google用于实现MVVM的官方组件。ViewModel在屏幕旋转后依然存活:配置更改时,Activity被销毁并重新创建,而ViewModel保留在内存中。新的Activity实例通过ViewModelProvider获取相同的ViewModel。ViewModel没有引用Activity、Context或View——它是纯粹的,可以在没有Robolectric的情况下通过单元测试进行测试。

kotlin
// 使用StateFlow的ViewModel——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与StateFlow——LiveData(2017)是第一个反应式Jetpack组件,针对Activity生命周期进行了优化:在onStop时自动取消订阅。StateFlow(2021)是Kotlin Flow的实现,不绑定生命周期,但需要通过lifecycleScope手动取消订阅。StateFlow支持coroutines、concat、map以及其他LiveData中没有的Flow操作符。在IT Sectr,我们为所有新的ViewModel使用StateFlow——它更短、更强大,并且与协程集成得更好。

DataBinding和ViewBinding——DataBinding通过@{viewModel.user.name}直接在布局中将ViewModel与XML绑定,消除了Activity中的代码。ViewBinding生成类型安全的类用于访问View。Google推荐简单项目使用ViewBinding,复杂数据绑定的项目使用DataBinding。在Jetpack Compose中,不需要DataBinding——@Composable函数在State变化时自动重新渲染。

iOS中的MVVM:ObservableObject和SwiftUI

iOS中的MVVM通过Combine中的ObservableObject实现。ViewModel是一个继承ObservableObject的类,带有@Published字段。SwiftUI View通过@ObservedObject或@StateObject订阅ViewModel。当@Published属性发生变化时,SwiftUI自动重新渲染依赖于该属性的View。Apple于2019年在WWDC上与Combine一起发布了SwiftUI——从那时起,MVVM成为iOS官方推荐的模式。

swift
import SwiftUI
import Combine

// ViewModel——带@Published字段的ObservableObject
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与@ObservedObject——@StateObject创建ViewModel并管理其生命周期(在View的生命周期内只创建一次)。@ObservedObject——ViewModel在外部创建并传递给View。WWDC 2022推荐使用@StateObject进行创建,使用@ObservedObject在Views之间传递ViewModel。在iOS 17(2023)中出现了@Observable——一个自动订阅并消除@Published注解的宏。@Observable是Combine的演进,使iOS开发更接近Kotlin Flow的反应式特性。

UIKit + MVVM——对于基于UIKit(没有SwiftUI)的项目,MVVM通过Combine和@Published在UIViewController中使用sink()进行订阅来实现。ViewModel相同,View是订阅了@Published的UIViewController。Combine从iOS 13(2019)开始可用,并且内置于系统中——不需要额外的依赖。根据Apple Developer Survey(2025),45%的iOS项目即使在UIKit中也使用Combine,35%使用SwiftUI + Combine,20%使用RxSwift(遗留系统)。

MVVM与MVP的比较:优势和劣势

MVVM在三个关键方面优于MVP:没有ViewContract接口、自动订阅管理以及屏幕旋转后的存活。在MVP中,每个屏幕需要ViewContract接口 + Presenter类 + 在onStart/onStop中进行订阅/取消订阅。在MVVM中,只创建ViewModel——在Activity中的订阅通过observe()完成,无需手动detach()。

标准MVPMVVM
ViewContract接口每个屏幕1个不需要
订阅管理手动attach/detach自动(生命周期感知)
屏幕旋转Retain-fragmentViewModel存活
测试Mock ViewContract无依赖的纯净类
反应式Presenter中的回调LiveData/StateFlow/Combine

MVVM的缺点——调试反应式链的复杂性以及错误订阅导致内存泄漏的风险。LiveData解决了生命周期安全问题,StateFlow需要lifecycleScope,Combine需要带有AnyCancellable的sink。在MVP中,所有调用都是显式的(view.showUser),而在MVVM中,数据通过反应式流到达——跟踪需要在subscribe闭包中设置调试断点。在具有多个StateFlow的大型ViewModel中,如果View没有订阅特定的Flow,可能会错过UI更新。

何时MVP仍然更好——在最低Android版本低于API 21(Android 5)的项目中,Jetpack ViewModel在没有AndroidX的情况下不可用,以及在纯UIKit且没有Combine(iOS 12及以下)的项目中。对于整个代码库已经基于MVP的遗留项目,完全迁移到MVVM并不总是合理的——逐步将逻辑提取到服务中并维护MVP比在3个月内重写100个屏幕更便宜。

在Android和iOS上测试ViewModel

ViewModel通过单元测试进行测试,没有平台依赖——这是支持MVVM的主要论据。在Android中,ViewModel不包含Activity、Context或View——所有依赖(Repository、UseCase)通过构造函数传递,并被模拟对象替换。在iOS中,ObservableObject通过XCTest进行测试,无需启动应用程序,这提供了测试执行的稳定性和速度。

kotlin
// 使用MockK的Android ViewModel单元测试
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以类似方式进行测试:我们注入模拟的UserService,调用loadUser,通过XCTestExpectation检查状态。Combine Publisher通过XCTestCase使用wait(for: expectations, timeout: 1.0)进行测试。UserState结构——带有associated values的枚举——允许在操作后检查屏幕的确切状态。

代码覆盖率在IT Sectr的MVVM项目中,ViewModel和Repository的覆盖率为75-90%。ViewModel由单元测试覆盖,Repository由集成测试使用测试数据库覆盖。SwiftUI和Jetpack Compose中的View通过UI测试(XCUITest、Compose Test)覆盖关键场景。其余UI通过截图测试(Snapshot Testing)进行检查——这比UI测试更快,并且对显示正确性提供95%的信心。

常见问题

MVVM和MVP之间的主要区别是什么?

在MVVM中,ViewModel没有对View的引用——数据通过反应式机制(LiveData、StateFlow、@Published)传递。在MVP中,Presenter通过ViewContract接口直接调用View的方法。MVVM消除了ViewContract和手动的attach/detach,但需要理解反应式流。ViewModel在Android中屏幕旋转后仍然存活,Presenter需要retain-fragment。

Android上MVVM需要哪些库?

最小集合: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推荐在iOS中使用MVVM?

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:Combine中的AnyCancellable在持有它的对象被释放时自动取消订阅。SwiftUI @StateObject自动管理生命周期。主要规则:不要在ViewModel中存储对View/Context的引用,在清理时取消长时间运行的操作,在闭包中使用weak self。

为Android选择LiveData还是StateFlow?

StateFlow是现代选择。LiveData更简单且生命周期安全,但StateFlow更强大:与协程一起工作,支持flatMap、combine、filter,不需要@Nullable注解。唯一LiveData更受青睐的场景是与Java代码一起工作,因为StateFlow(Kotlin Flow-API)不可用。Google推荐在Kotlin新项目中使用StateFlow。

总结

  • MVVM(Model-View-ViewModel)——一种反应式模式,其中ViewModel没有对View的引用
  • ViewModel——屏幕旋转后存活,通过单元测试进行测试,独立于UI
  • Android——ViewModel + StateFlow + Kotlin Coroutines——来自Google的现代技术栈
  • iOS——ObservableObject + @Published + SwiftUI——MVVM的原生实现
  • MVVM vs MVP——MVVM消除了ViewContract和手动的attach/detach
  • 测试——ViewModel通过单元测试覆盖,没有平台依赖
  • 推荐——新项目使用MVVM;遗留系统维护使用MVP

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读