@StateObject یک Property Wrapper در SwiftUI برای ایجاد و مالکیت نمونه ObservableObject به طور مستقیم در view است. SwiftUI تضمین میکند که شی یک بار در طول چرخه حیات نما مقداردهی اولیه میشود و در رندرهای مجدد دوباره ایجاد نمیشود. طبق Apple Developer Documentation (2025)، @StateObject برای نماهای ریشهای که منبع داده ایجاد میکنند توصیه میشود. @StateObject انتخاب صحیح برای مالکیت ObservableObject در سلسلهمراتب SwiftUI است.
نکات اصلی
@StateObject یک Property Wrapper است که در SwiftUI 2.0 (iOS 14) ظاهر شد و قابلیتهای @ObservedObject و @State را ترکیب میکند. مانند @ObservedObject، در تغییرات ObservableObject مشترک میشود. مانند @State، تضمین میکند که دادهها از مقداردهی مجدد ساختار view جان سالم به در میبرند. @StateObject شی را یک بار در اولین ظهور view روی صفحه ایجاد میکند و آن را در heap SwiftUI ذخیره میکند.
قبل از ظهور @StateObject، توسعهدهندگان از @ObservedObject برای همه ObservableObjectها از جمله那些 ایجاد شده در viewها استفاده میکردند. این امر به از دست دادن مکرر داده هنگام بهروزرسانی view والد منجر میشد، زیرا ساختار view دوباره ایجاد میشد و نمونه @ObservedObject را نیز با خود میبرد. @StateObject با افزودن تضمین ثبات این مشکل را حل کرد.
قانون اصلی: @StateObject در نمایی که شی را ایجاد میکند در مقداردهنده پیشفرض (let model = ViewModel()) اعمال میشود. نماهای فرزند که این شی را دریافت میکنند از @ObservedObject استفاده میکنند. چنین تقسیمبندی یک منبع حقیقت واحد را در کل سلسلهمراتب تضمین میکند.
SwiftUI چرخه حیات @StateObject را از طریق مدیر ذخیرهسازی مشابه @State مدیریت میکند. در اولین ظهور view، SwiftUI حافظه را برای شی اختصاص میدهد و آن را در ناحیه پایدار ذخیره میکند. در رندرهای مجدد (فراخوانی body)، شی دوباره ایجاد نمیشود — از نمونه موجود استفاده میشود. شی تا زمانی که view در سلسلهمراتب است زنده میماند.
وقتی view از سلسلهمراتب حذف میشود، SwiftUI @StateObject را نابود میکند و deinit را فراخوانی میکند. با افزودن مجدد view به سلسلهمراتب، نمونه جدیدی ایجاد میشود. این نکته در طراحی مهم است: اگر نیاز به حفظ دادهها بین حذفهای view دارید، از لایه سرویس (singleton یا DI) یا @AppStorage برای ماندگاری استفاده کنید.
class TimerViewModel: ObservableObject {
@Published var seconds: Int = 0
private var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
}
deinit {
timer?.invalidate()
}
}
struct TimerView: View {
@StateObject var viewModel = TimerViewModel()
var body: some View {
Text("\(viewModel.seconds)s")
.onAppear { viewModel.start() }
}
}
در مثال، TimerViewModel از طریق @StateObject ایجاد میشود و تا زمانی که TimerView روی صفحه است زنده میماند. Timer در onAppear شروع میشود و در deinit متوقف میشود. اگر از @ObservedObject استفاده میشد، در هر رندر TimerView یک TimerViewModel جدید با ثانیه = 0 ایجاد میشد و تایمر هرگز به درستی کار نمیکرد. @StateObject تضمین میکند که viewModel唯一 و پایدار است.
انتخاب بین @StateObject و @ObservedObject بستگی به این دارد که چه کسی مالک شی است. اگر view شی را ایجاد میکند — @StateObject. اگر view شی آماده را دریافت میکند — @ObservedObject. این قانون آنقدر مهم است که Xcode هنگام استفاده از @StateObject در view فرزندی که شی را از طریق مقداردهنده دریافت میکند اخطار میدهد.
| موقعیت | Wrapper توصیه شده |
|---|---|
| View مدل را از طریق ViewModel() ایجاد میکند | @StateObject |
| View مدل را از والد دریافت میکند | @ObservedObject |
| مدل در یک view استفاده میشود | @StateObject |
| مدل از طریق Environment منتقل میشود | @EnvironmentObject |
| مدل برای پیشنمایش نیاز است | @ObservedObject + mock |
در عمل، در شروع پروژه اغلب از @StateObject در view ریشه و @ObservedObject در همه viewهای فرزند استفاده میشود. با رشد برنامه، بخشی از @StateObject ممکن است برای سادهسازی سلسلهمراتب با @EnvironmentObject جایگزین شود. با این حال، @StateObject بهترین انتخاب برای صفحههای ماژولار با منطق خاص خود باقی میماند.
الگوی اول — MVVM با @StateObject. ViewModel به عنوان ObservableObject در view از طریق @StateObject ایجاد میشود. ViewModel شامل ویژگیهای @Published و منطق تجاری است. View در تغییرات مشترک میشود و رابط را بهروز میکند. این رویکرد ایزولهسازی قابل تست را فراهم میکند: ViewModel را میتوان بدون UI با ایجاد مستقیم نمونه تست کرد.
الگوی دوم — @StateObject با وابستگیها. اگر ViewModel به سرویسها نیاز دارد، از مقداردهی با پارامترها استفاده کنید. مثلاً @StateObject var viewModel = UserViewModel(api: APIClient.shared). با این حال احتیاط کنید: پارامترها در هر رندر body محاسبه میشوند، اما شی فقط یک بار ایجاد میشود. SwiftUI مقداردهیهای بعدی @StateObject را نادیده میگیرد.
الگوی سوم — @StateObject تودرتو. در SwiftUI میتوان چند @StateObject در یک view داشت، اما این به ندرت موجه است. معمولاً یک @StateObject مسئول کل مجموعه دادههای view است. اگر منطق بیش از حد پیچیده میشود، آن را به ترکیبی از سرویسهای @ObservedObject درون یک @StateObject تقسیم کنید.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
در مثال، AppView دو @StateObject ایجاد میکند: NavigationRouter برای مدیریت ناوبری و AuthViewModel برای احراز هویت. هر دو شی از طریق environmentObject به Environment تزریق میشوند. هر view فرزندی میتواند از طریق @EnvironmentObject بدون عبور از زنجیره مقداردهندهها به آنها دسترسی پیدا کند.
@StateObject از مقداردهی با هر پارامتری پشتیبانی میکند، اما با یک ویژگی مهم: مقداردهنده فقط یک بار فراخوانی میشود. در رندرهای مجدد body، مقدار جدید پارامترها نادیده گرفته میشود. این بدان معناست که اگر @State var id: Int = 5 را به @StateObject var vm = ViewModel(id: id) منتقل کنید، با تغییر id، ViewModel مقدار جدید را دریافت نمیکند.
برای حل این مشکل از onReceive یا onAppear برای همگامسازی استفاده کنید. تغییرات پارامتر را درون ViewModel از طریق Combine مشترک شوید یا پارامترها را از طریق متد .onChange(of:) در سطح view منتقل کنید. جایگزین — استفاده از @ObservedObject به جای @StateObject اگر شی باید به تغییرات خارجی واکنش پویا نشان دهد.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
رویکرد صحیح: DetailView itemId را به عنوان ویژگی let دریافت میکند (از طریق مقداردهنده ساختار منتقل میشود)، و @StateObject DetailViewModel را بدون پارامتر ایجاد میکند. در onAppear متد load(id:) فراخوانی میشود که دادهها را برای ID منتقل شده بارگذاری میکند. این تضمین میکند که ViewModel توسط مکانیزم @StateObject ایجاد شده است، اما دادهها در هر ظهور view با ID جاری بارگذاری میشوند.
اشتباه اصلی — استفاده از @StateObject در viewهای فرزند که شی را از والد دریافت میکنند. اگر ParentView @StateObject model ایجاد کند و ChildView @StateObject var model: ModelType (با پارامتر پیشفرض) اعلام کند، ChildView نمونه مستقل خود را ایجاد خواهد کرد. اشیاء والد و فرزند مرتبط نخواهند بود و تغییرات در یکی در دیگری منعکس نخواهد شد.
اشتباه دوم — قرار دادن @StateObject در List یا ForEach. هر عنصر لیست @StateObject خود را ایجاد میکند که منجر به نمونههای مستقل متعدد میشود. برای لیستها، درست است که یک ObservableObject را به همه عناصر از طریق @ObservedObject منتقل کنید یا از ساختارهای Identifiable با @State درون List استفاده کنید.
مشکل سوم — عدم پاکسازی در deinit. @StateObject در کل چرخه حیات view زنده میماند. اگر شی تایمرها، اشتراکهای Combine یا درخواستهای شبکه ایجاد میکند، deinit باید آنها را لغو کند. در غیر این صورت نشت حافظه و ادامه کار پس زمینه پس از بسته شدن صفحه اجتنابناپذیر است. همیشه از Cancellable store Combine یا invalidate تایمرها در deinit استفاده کنید.
سوالات متداول
@StateObject در SwiftUI 2.0 در WWDC 2020 همراه با iOS 14، macOS 11، watchOS 7 و tvOS 14 اضافه شد. قبل از آن @ObservedObject تنها راه کار با ObservableObject بود که منجر به باگهای مکرر از دست دادن داده میشد.
خیر، @StateObject از انواع Optional پشتیبانی نمیکند. شی باید در زمان اعلام مقداردهی شود. اگر شی اختیاری نیاز دارید، از @ObservedObject یا @EnvironmentObject با نوع اختیاری استفاده کنید.
print(#function) را در مقداردهنده و deinit ObservableObject اضافه کنید. اگر در رندرهای مجدد init فراخوانی نشود — @StateObject به درستی کار میکند. اگر init هر بار فراخوانی میشود — @ObservedObject را با @StateObject جایگزین کنید.
بله، @StateObject در viewهای SwiftUI تعبیه شده در UIKit از طریق UIHostingController کار میکند. چرخه حیات شی به SwiftUI view مرتبط است، نه به UIViewController. اگر SwiftUI view جایگزین شود، @StateObject نابود میشود.
چند @StateObject کوچک با مسئولیتهای جداگانه. این قابلیت تست، استفاده مجدد و عملکرد را بهبود میبخشد — با تغییر یک شی، فقط بخشهای مشترک رابط دوباره ترسیم میشوند، نه کل view.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید