MVVM (Model-View-ViewModel) — الگوی معماری است که در آن ViewModel جایگزین Presenter میشود و از مکانیسمهای واکنشگرا برای ارتباط با View استفاده میکند: ObservableObject در SwiftUI، LiveData/StateFlow در Android. ViewModel هیچ ارجاعی به View ندارد — دادهها از طریق اشتراکگذاری منتقل میشوند که نیاز به رابطهای ViewContract را از بین میبرد و تستگیری را حتی سادهتر میکند. اپل از سال 2019 MVVM با SwiftUI را توصیه میکند، گوگل — MVVM با Jetpack را به عنوان معماری رسمی Android. جزئیات بیشتر در Android Architecture Guide.
نکات اصلی
MVVM (Model-View-ViewModel) — الگوی معماری است که توسط John Gossman در سال 2005 برای 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 جدید به سادگی روی دادههای بهروز اشتراک میکند. در IT Sectr ما از سال 2020 در همه پروژههای جدید خود از MVVM استفاده میکنیم — کد قابل پیشبینیتر شد، تستها پایدارتر شدند.
| مؤلفه | مسئولیت | پلتفرم |
|---|---|---|
| Model | دادهها، منطق تجاری، مخازن | Android/iOS |
| View | نمایش، اشتراک ViewModel | Activity/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 لازم است از بین میبرد.
ViewModel از Jetpack — مؤلفه رسمی از گوگل برای پیادهسازی MVVM. ViewModel از چرخش صفحه جان سالم به در میبرد: هنگام تغییر پیکربندی، Activity از بین میرود و دوباره ایجاد میشود، در حالی که ViewModel در حافظه باقی میماند. نمونه جدید Activity همان ViewModel را از طریق ViewModelProvider دریافت میکند. ViewModel هیچ ارجاعی به Activity، Context یا View ندارد — خالص است و با تستهای واحد بدون Robolectric آزمایش میشود.
// 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 در مقابل StateFlow — LiveData (2017) — اولین مؤلفه واکنشگرای Jetpack، بهینهسازی شده برای چرخه حیات Activity: لغو اشتراک خودکار در onStop. StateFlow (2021) — پیادهسازی Kotlin Flow، غیرمرتبط با چرخه حیات، اما نیاز به لغو اشتراک دستی از طریق lifecycleScope دارد. StateFlow از coroutines، concat، map و سایر عملگرهای Flow پشتیبانی میکند که در LiveData وجود ندارد. در IT Sectr ما از StateFlow برای همه ViewModelهای جدید استفاده میکنیم — کوتاهتر، قدرتمندتر و بهتر با کوروتینها یکپارچه میشود.
DataBinding و ViewBinding — DataBinding ViewModel را با XML از طریق @{viewModel.user.name} مستقیماً در layout متصل میکند و کد را در Activity حذف میکند. ViewBinding یک کلاس نوع-ایمن برای دسترسی به View تولید میکند. گوگل ViewBinding را برای پروژههای ساده و DataBinding را برای پروژههای با اتصال داده پیچیده توصیه میکند. در Jetpack Compose، DataBinding لازم نیست — توابع @Composable هنگام تغییر State بهطور خودکار دوباره ترسیم میشوند.
MVVM در iOS از طریق ObservableObject از Combine پیادهسازی میشود. ViewModel — کلاسی که از ObservableObject ارثبری میکند، با فیلدهای @Published. SwiftUI View از طریق @ObservedObject یا @StateObject روی ViewModel اشتراک میکند. هنگام تغییر ویژگی @Published، SwiftUI بهطور خودکار Viewای را که به این ویژگی وابسته است دوباره ترسیم میکند. اپل SwiftUI را در سال 2019 در WWDC همراه با Combine معرفی کرد — از آن لحظه MVVM به طور رسمی به عنوان الگوی توصیهشده برای iOS تبدیل شد.
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 در مقابل @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 حتی در UIKit از Combine استفاده میکنند، 35% از SwiftUI + Combine استفاده میکنند، 20% — RxSwift (legacy).
MVVM از MVP بهتر است در سه جنبه کلیدی: عدم وجود رابطهای ViewContract، مدیریت خودکار اشتراکها و تحمل چرخش صفحه. در MVP هر صفحه نیاز به رابط ViewContract + کلاس Presenter + اشتراک/لغو اشتراک در onStart/onStop دارد. در MVVM فقط ViewModel ایجاد میشود — اشتراک در Activity از طریق observe() بدون detach دستی انجام میشود.
| معیار | MVP | MVVM |
|---|---|---|
| رابطهای ViewContract | ۱ در هر صفحه | نیاز نیست |
| مدیریت اشتراکها | دستی attach/detach | خودکار (lifecycle-aware) |
| چرخش صفحه | Retain-fragment | ViewModel تحمل میکند |
| تستگیری | Mock ViewContract | کلاس خالص بدون وابستگی |
| واکنشگرایی | Callback در Presenter | LiveData/StateFlow/Combine |
معایب MVVM — پیچیدگی اشکالزدایی زنجیرههای واکنشگرا و خطر نشت حافظه در صورت اشتراک نادرست. LiveData مشکل ایمنی چرخه حیات را حل میکند، StateFlow نیاز به lifecycleScope دارد، Combine — sink با AnyCancellable. در MVP همه فراخوانیها صریح هستند (view.showUser)، در MVVM دادهها از طریق جریان واکنشگرا میآیند — ردیابی نیاز به نقاط توقف debug در closure subscribe دارد. در ViewModelهای بزرگ با StateFlowهای متعدد، اگر View روی Flow خاصی مشترک نباشد، میتوان بهروزرسانی UI را از دست داد.
چه زمانی MVP همچنان بهتر است — در پروژههایی با حداقل نسخه Android زیر API 21 (Android 5)، که Jetpack ViewModel بدون AndroidX در دسترس نیست، و در پروژههای روی UIKit خالص بدون Combine (iOS 12 و پایینتر). برای پروژههای legacy که کل پایه کد قبلاً روی MVP است، انتقال کامل به MVVM همیشه توجیهپذیر نیست — نگهداری MVP با انتقال تدریجی منطق به سرویسها ارزانتر از بازنویسی 100 صفحه در 3 ماه است.
ViewModel با تستهای واحد بدون وابستگیهای پلتفرمی آزمایش میشود — این استدلال اصلی به نفع MVVM است. در Android، ViewModel حاوی Activity، Context یا View نیست — همه وابستگیها (Repository، UseCase) از طریق سازنده منتقل میشوند و با اشیاء mock جایگزین میشوند. در iOS، ObservableObject از طریق XCTest بدون راهاندازی برنامه آزمایش میشود که پایداری و سرعت اجرای تستها را فراهم میکند.
// تست واحد 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 را فراخوانی میکنیم، وضعیت را از طریق XCTestExpectation بررسی میکنیم. Combine Publisher از طریق XCTestCase با wait(for: expectations, timeout: 1.0) آزمایش میشود. ساختار UserState — enum با associated values — امکان بررسی وضعیت دقیق صفحه پس از عملیات را فراهم میکند.
پوشش کد در پروژههای IT Sectr روی MVVM 75-90% برای ViewModel و Repository است. ViewModel با تستهای واحد، Repository با تستهای یکپارچهسازی با پایگاه داده آزمایشی پوشش داده میشود. View در SwiftUI و Jetpack Compose برای سناریوهای بحرانی با تستهای UI (XCUITest، Compose Test) آزمایش میشود. بقیه UI با تستهای اسکرینشات (Snapshot Testing) بررسی میشود — این سریعتر از تستهای UI است و 95% اطمینان از صحت نمایش میدهد.
سوالات متداول
در MVVM، ViewModel ارجاعی به View ندارد — دادهها از طریق مکانیسمهای واکنشگرا (LiveData، StateFlow، @Published) منتقل میشوند. در MVP، Presenter مستقیماً متدهای View را از طریق رابط ViewContract فراخوانی میکند. MVVM ViewContract و attach/detach دستی را حذف میکند، اما نیاز به درک جریانهای واکنشگرا دارد. ViewModel در Android از چرخش صفحه جان سالم به در میبرد، Presenter نیاز به retain-fragment دارد.
حداقل مجموعه: lifecycle-viewmodel-ktx (ViewModel)، lifecycle-livedata-ktx یا kotlinx-coroutines-core (StateFlow). برای تزریق — Hilt یا Koin. برای عملیات ناهمگام — Kotlin Coroutines. برای اتصال داده پیچیده — DataBinding. در Jetpack Compose (توصیهشده توسط گوگل از 2022) compose-runtime و lifecycle-viewmodel-compose کافی است.
SwiftUI (2019) برای معماری واکنشگرا طراحی شده است: @State و @Published هنگام تغییر دادهها بهطور خودکار View را دوباره ترسیم میکنند. MVVM تطبیق طبیعی با SwiftUI است: View — @ViewBuilder، ViewModel — ObservableObject. اپل MVVM را به عنوان تنها الگو تحمیل نمیکند، اما تمام مواد آموزشی از سال 2019 از ViewModel + SwiftUI استفاده میکنند. برای UIKit، اپل MVC یا Coordinator را توصیه میکند.
Android: viewModelScope هنگام پاکسازی ViewModel به طور خودکار کوروتینها را لغو میکند. iOS: AnyCancellable از Combine هنگام آزاد شدن شیء نگهدارنده به طور خودکار اشتراک را لغو میکند. SwiftUI @StateObject چرخه حیات را به طور خودکار مدیریت میکند. قوانین اصلی: ارجاعات به View/Context را در ViewModel ذخیره نکنید، عملیات طولانی را هنگام پاکسازی لغو کنید، از weak self در closureها استفاده کنید.
StateFlow — انتخاب مدرن. LiveData سادهتر و ایمن برای چرخه حیات است، اما StateFlow قدرتمندتر است: با کوروتینها کار میکند، از flatMap، combine، filter پشتیبانی میکند، نیازی به حاشیهنویسی @Nullable ندارد. تنها سناریویی که LiveData ارجحیت دارد — کار با کد Java، جایی که StateFlow (Kotlin Flow-API) در دسترس نیست. گوگل StateFlow را برای پروژههای جدید در Kotlin توصیه میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید