@StateObject је Property Wrapper у SwiftUI-ју за креирање и власништво над инстанцом ObservableObject-а директно у view-у. SwiftUI гарантује да се објекат иницијализује једном током животног циклуса приказа и да се не поново креира приликом поновних рендеровања. Према Apple Developer Documentation (2025), @StateObject се препоручује за коренске view-ове који креирају извор података. @StateObject је прави избор за власништво над ObservableObject-ом у SwiftUI хијерархији.
Главне тачке
@StateObject је Property Wrapper који се појавио у SwiftUI 2.0 (iOS 14) и који комбинује могућности @ObservedObject-а и @State-а. Као @ObservedObject, претплаћује се на промене ObservableObject-а. Као @State, гарантује да подаци преживљавају вишеструке иницијализације структуре view-а. @StateObject креира објекат једном приликом првог појављивања view-а на екрану и чува га на SwiftUI хеап-у.
Пре појаве @StateObject-а, програмери су користили @ObservedObject за све ObservableObject-е, укључујући оне креиране у view-овима. То је доводило до честог губитка података приликом ажурирања родитељског view-а, када се структура view-а поново креирала, носећи са собом и @ObservedObject инстанцу. @StateObject је решио овај проблем додавањем гаранције стабилности.
Основно правило: @StateObject се примењује у view-у који креира објекат у подразумеваном иницијализатору (let model = ViewModel()). Дечији view-ови који добијају овај објекат користе @ObservedObject. Таква подела гарантује јединствени извор истине у целој хијерархији.
SwiftUI управља животним циклусом @StateObject-а путем менаџера складиштења, аналогно @State-у. Приликом првог појављивања view-а, SwiftUI додељује меморију за објекат и чува га у перзистентној области. Приликом поновних рендеровања (позив body), објекат се не поново креира — користи се постојећа инстанца. Објекат живи док је view у хијерархији.
Када се view уклони из хијерархије, SwiftUI уништава @StateObject, позивајуći deinit. Приликом поновног додавања view-а у хијерархију, креира се нова инстанца. Ово је важно узети у обзир при дизајнирању: ако треба сачувати податке између уклањања view-а, користите сервисни слој (синглтон или 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 гарантује да je 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 за аутентификацију. Оба објекта се убризгавају у Environment путем environmentObject-а. Било који дечији 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 их мора отказати. У супротном, цурење меморије и наставак рада у позадини након затварања екрана су неизбежни. Увек користите Combine Cancellable store или 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 ради у SwiftUI view-овима уграђеним у UIKit путем UIHostingController-а. Животни циклус објекта је везан за SwiftUI view, а не за UIViewController. Ако се SwiftUI view замени, @StateObject се уништава.
Више малих @StateObject-а са подељеном одговорношћу. Ово побољшава тестираност, поновну употребу и перформансе — при промени једног објекта, само претплаћени делови интерфејса се поново исцртавају, а не цео view.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође