@StateObject е property wrapper в SwiftUI, който създава и притежава екземпляр на ObservableObject през целия жизнен цикъл на View. Когато View се появи за първи път на екрана, @StateObject инициализира обекта и го съхранява, докато View не бъде премахнато от паметта. Това гарантира, че данните няма да бъдат нулирани при преизграждане на интерфейса — например при смяна на тема или актуализиране на родителското View. Според Apple Developer Documentation (2025), @StateObject трябва да се използва като основен източник на истина (source of truth) за ObservableObject в йерархията на SwiftUI, докато дъщерните View получават вече създадения обект чрез @ObservedObject или @EnvironmentObject.
Основни точки
@StateObject е property wrapper, въведен в iOS 14, който позволява на View да създава и притежава екземпляр на клас, съответстващ на протокола ObservableObject. За разлика от @State, който работи със стойностни типове (структури), @StateObject е предназначен за референтни типове — класове, които могат да уведомяват SwiftUI за промени в своите свойства.
Когато View използва @StateObject var viewModel: MyViewModel, SwiftUI автоматично създава екземпляр на MyViewModel при първото показване на View и го съхранява в специално хранилище на framework-а. При всяка актуализация на View (например при промяна на родителското състояние), SwiftUI не пресъздава обекта — използва съществуващия екземпляр, докато View не бъде премахнато от йерархията.
Според Apple WWDC Session 10137 (2024), @StateObject решава проблема със загубата на данни при преизграждане на View, който съществуваше в iOS 13, когато разработчиците трябваше да създават ObservableObject в родителското View и да го предават чрез инициализатора. Това водеше до дублиране на код и риск от случайно пресъздаване на обекта.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Брой: \(viewModel.count)")
Button("Увеличи", action: viewModel.increment)
}
}
}
Механизмът на @StateObject се основава на интеграцията на SwiftUI с framework-а Combine. Когато ObservableObject маркира своите свойства с атрибута @Published, SwiftUI автоматично се абонира за промени чрез publisher-а, вграден в протокола ObservableObject. При промяна на публикувано свойство, обектът изпраща сигнал чрез objectWillChange publisher, което задейства прерисуване на всички View, наблюдаващи този обект.
SwiftUI съхранява екземпляра на ObservableObject в специално хранилище, обвързано с конкретен екземпляр на View. Това хранилище се създава веднъж при първото рендиране и съществува до унищожаването на View. Точно поради тази причина @StateObject гарантира стабилността на референцията към обекта — SwiftUI управлява паметта автоматично, без да разчита на инициализатора на View.
Според objc.io — Thinking in SwiftUI (2025), вътрешната имплементация на @StateObject използва механизъм, подобен на @State, но за референтни типове: SwiftUI създава boxing обвивка около обекта и управлява жизнения му цикъл чрез собствен алокатор, оптимизиран за чести преизграждания на йерархията на View.
Основната разлика между @StateObject и @ObservedObject е кой притежава обекта. @StateObject създава и съхранява обекта — той е собственик. @ObservedObject само наблюдава обект, който е създаден някъде другаде и предаден чрез инициализатор или свойство.
| Характеристика | @StateObject | @ObservedObject |
|---|---|---|
| Притежание | Създава и притежава обекта | Само наблюдава |
| Инициализация | Вътре в View чрез init/default | Отвън, предава се чрез параметър |
| Жизнен цикъл | Обвързан с жизнения цикъл на View | Не се контролира от View |
| Пресъздаване | Не се пресъздава при актуализация | Може да бъде заменен отвън |
| Версия на iOS | iOS 14+ | iOS 13+ |
Правилото е просто: ако View създава ObservableObject — използвайте @StateObject. Ако View само получава вече готов обект от родителя — използвайте @ObservedObject. Нарушаването на това правило води или до загуба на данни (ако използвате @ObservedObject за притежание), или до прекомерно създаване на обекти (ако използвате @StateObject за наблюдение).
@StateObject трябва да се използва в онези View, които са източник на истина за определен набор от данни. Типични сценарии включват екрани със собствен view model, коренови екрани на навигационни стекове и модални презентации, управляващи собствено състояние.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Запази") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Инициализацията на @StateObject с параметри изисква специален синтаксис, тъй като SwiftUI управлява създаването на обекта независимо. Не можете просто да предадете параметри на инициализатора — трябва да използвате escaping затваряне или отделен метод за създаване.
Според Swift by Sundell (2024), най-чистият метод е използването на фабричен метод или затваряне, което SwiftUI ще извика при първото създаване на обекта. Алтернативен подход — инициализирайте ObservableObject в родителското View и го предайте чрез @StateObject, използвайки стандартния инициализатор.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
Важно е да запомните, че инициализаторът на View с @StateObject трябва да използва долна черта пред името на свойството (_viewModel) за достъп до самия property wrapper, а не до неговата стойност. Това е стандартен модел в Swift за работа с property wrappers в инициализатори.
Най-често срещаната грешка е използването на @ObservedObject вместо @StateObject за View, което трябва да притежава обекта. В този случай при всяко преизграждане на родителя, обектът ще бъде пресъздаден, което води до загуба на всички натрупани данни. Тази грешка е особено коварна в сложни йерархии с NavigationStack или TabView.
За да избегнете тези проблеми, следвайте простото правило: един @StateObject на източник на истина. Ако данните трябва да бъдат споделени между няколко екрана — създайте @StateObject веднъж в кореновото View и го предавайте чрез @ObservedObject или @EnvironmentObject на дъщерните елементи.
// ❌ Грешно: @ObservedObject за притежаване на обект
struct BadView: View {
@ObservedObject var vm = ViewModel() // ще бъде пресъздаден при всяка актуализация!
}
// ✅ Правилно: @StateObject за притежаване
struct GoodView: View {
@StateObject var vm = ViewModel() // създаден веднъж за живота на View
}
Често задавани въпроси
@State работи със стойностни типове (структури, низове, числа) и съхранява стойността директно в хранилището на SwiftUI. @StateObject работи с референтни типове — класове, съответстващи на ObservableObject. @State е подходящ за прости локални състояния, @StateObject — за сложни обекти с логика и публикувани свойства.
Не, @StateObject е достъпен само от iOS 14 нагоре. За iOS 13 използвайте @ObservedObject и създайте ObservableObject в родителското View чрез @State с ръчно управление на жизнения цикъл. Алтернатива — използвайте @State с struct вместо class за данни, които не изискват референтна семантика.
Дъщерното View ще създаде свое собствено копие на ObservableObject, напълно независимо от родителското. Промените в едното няма да се отразят в другото. Това почти винаги е грешка: използвайте @ObservedObject за получаване на обект от родителя и @StateObject само за създаване на нов обект вътре в View.
Обектът се унищожава, когато View, което го е създало, бъде напълно премахнато от йерархията на SwiftUI. За екран в NavigationStack това се случва при pop от навигационния стек. За модален прозорец — при затварянето му. За TabView — при превключване на раздел, ако View не се кешира.
Използвайте персонализиран init с достъп до property wrapper чрез долна черта: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Този модел позволява предаването на всякакви параметри на ObservableObject, като същевременно запазва гаранцията за еднократно създаване на обекта за времето на живот на View.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също