@StateObject adalah Property Wrapper di SwiftUI untuk membuat dan memiliki instance ObservableObject langsung di view. SwiftUI menjamin bahwa objek diinisialisasi satu kali selama siklus hidup tampilan dan tidak dibuat ulang saat render berulang. Menurut Apple Developer Documentation (2025), @StateObject direkomendasikan untuk view akar yang membuat sumber data. @StateObject adalah pilihan tepat untuk memiliki ObservableObject dalam hierarki SwiftUI.
Poin Utama
@StateObject adalah Property Wrapper yang muncul di SwiftUI 2.0 (iOS 14) yang menggabungkan kemampuan @ObservedObject dan @State. Seperti @ObservedObject, ia berlangganan perubahan ObservableObject. Seperti @State, ia menjamin bahwa data bertahan dari inisialisasi berulang struktur view. @StateObject membuat objek sekali saat pertama kali view muncul di layar dan menyimpannya di heap SwiftUI.
Sebelum munculnya @StateObject, pengembang menggunakan @ObservedObject untuk semua ObservableObject, termasuk yang dibuat di view. Ini menyebabkan seringnya kehilangan data saat view induk diperbarui, ketika struktur view dibuat ulang, membawa serta instance @ObservedObject. @StateObject memecahkan masalah ini dengan menambahkan jaminan stabilitas.
Aturan utama: @StateObject diterapkan di view yang membuat objek di inisialisator default (let model = ViewModel()). View anak yang menerima objek ini menggunakan @ObservedObject. Pemisahan semacam itu menjamin satu sumber kebenaran di seluruh hierarki.
SwiftUI mengelola siklus hidup @StateObject melalui manajer penyimpanan, mirip dengan @State. Saat pertama kali view muncul, SwiftUI mengalokasikan memori untuk objek dan menyimpannya di area persisten. Saat render berulang (pemanggilan body), objek tidak dibuat ulang — instance yang ada digunakan. Objek hidup selama view berada dalam hierarki.
Ketika view dihapus dari hierarki, SwiftUI menghancurkan @StateObject, memanggil deinit. Saat view ditambahkan kembali ke hierarki, instance baru dibuat. Ini penting dipertimbangkan saat merancang: jika perlu menyimpan data di antara penghapusan view, gunakan lapisan layanan (singleton atau DI) atau @AppStorage untuk persistensi.
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() }
}
}
Dalam contoh, TimerViewModel dibuat melalui @StateObject dan hidup selama TimerView di layar. Timer dimulai di onAppear dan berhenti di deinit. Jika @ObservedObject digunakan, setiap render TimerView akan membuat TimerViewModel baru dengan detik = 0 dan timer tidak akan pernah berfungsi dengan benar. @StateObject menjamin bahwa viewModel unik dan stabil.
Pilihan antara @StateObject dan @ObservedObject tergantung pada siapa yang memiliki objek. Jika view membuat objek — @StateObject. Jika view menerima objek jadi — @ObservedObject. Aturan ini sangat penting sehingga Xcode mengeluarkan peringatan saat menggunakan @StateObject di view anak yang menerima objek melalui inisialisator.
| Situasi | Wrapper yang direkomendasikan |
|---|---|
| View membuat model melalui ViewModel() | @StateObject |
| View menerima model dari induk | @ObservedObject |
| Model digunakan di satu view | @StateObject |
| Model diteruskan melalui Environment | @EnvironmentObject |
| Model diperlukan untuk pratinjau | @ObservedObject + mock |
Dalam praktiknya, di awal proyek sering digunakan @StateObject di view akar dan @ObservedObject di semua view anak. Seiring pertumbuhan aplikasi, sebagian @StateObject dapat diganti dengan @EnvironmentObject untuk menyederhanakan hierarki. Namun, @StateObject tetap menjadi pilihan terbaik untuk layar modular dengan logika sendiri.
Pola pertama — MVVM dengan @StateObject. ViewModel sebagai ObservableObject dibuat di view melalui @StateObject. ViewModel berisi properti @Published dan logika bisnis. View berlangganan perubahan dan memperbarui antarmuka. Pendekatan ini memberikan isolasi yang dapat diuji: ViewModel dapat diuji tanpa UI dengan membuat instance secara langsung.
Pola kedua — @StateObject dengan dependensi. Jika ViewModel memerlukan layanan, gunakan inisialisasi dengan parameter. Misalnya, @StateObject var viewModel = UserViewModel(api: APIClient.shared). Namun berhati-hatilah: parameter dihitung setiap kali render body, tetapi objek dibuat hanya sekali. SwiftUI mengabaikan inisialisasi @StateObject berikutnya.
Pola ketiga — @StateObject bersarang. Di SwiftUI, Anda dapat memiliki beberapa @StateObject dalam satu view, tetapi ini jarang dibenarkan. Biasanya satu @StateObject bertanggung jawab atas seluruh kumpulan data view. Jika logika menjadi terlalu kompleks, pecahlah menjadi komposisi layanan @ObservedObject di dalam satu @StateObject.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
Dalam contoh, AppView membuat dua @StateObject: NavigationRouter untuk mengelola navigasi dan AuthViewModel untuk autentikasi. Kedua objek disuntikkan ke Environment melalui environmentObject. Setiap view anak dapat mengaksesnya melalui @EnvironmentObject tanpa melewati rantai inisialisator.
@StateObject mendukung inisialisasi dengan parameter apa pun, tetapi dengan fitur penting: inisialisator dipanggil hanya sekali. Saat render ulang body, nilai baru parameter diabaikan. Ini berarti jika Anda meneruskan @State var id: Int = 5 ke @StateObject var vm = ViewModel(id: id), saat id berubah, ViewModel tidak akan menerima nilai baru.
Untuk mengatasi masalah ini, gunakan onReceive atau onAppear untuk sinkronisasi. Berlangganlah perubahan parameter di dalam ViewModel melalui Combine atau teruskan parameter melalui metode .onChange(of:) di tingkat view. Alternatif — gunakan @ObservedObject alih-alih @StateObject jika objek harus bereaksi secara dinamis terhadap perubahan eksternal.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
Pendekatan yang benar: DetailView menerima itemId sebagai properti let (diteruskan melalui inisialisator struktur), dan @StateObject membuat DetailViewModel tanpa parameter. Di onAppear, metode load(id:) dipanggil, yang memuat data untuk ID yang diteruskan. Ini menjamin bahwa ViewModel dibuat oleh mekanisme @StateObject, tetapi data dimuat setiap kali view muncul dengan ID saat ini.
Kesalahan utama — menggunakan @StateObject di view anak yang menerima objek dari induk. Jika ParentView membuat @StateObject model, dan ChildView mendeklarasikan @StateObject var model: ModelType (dengan parameter default), maka ChildView akan membuat instance independen sendiri. Objek induk dan anak tidak akan terhubung, dan perubahan di satu objek tidak akan tercermin di objek lainnya.
Kesalahan kedua — menempatkan @StateObject di List atau ForEach. Setiap elemen daftar membuat @StateObject sendiri, yang mengarah ke banyak instance independen. Untuk daftar, yang benar adalah meneruskan satu ObservableObject ke semua elemen melalui @ObservedObject atau menggunakan struktur Identifiable dengan @State di dalam List.
Masalah ketiga — kurangnya pembersihan di deinit. @StateObject hidup sepanjang siklus hidup view. Jika objek membuat timer, langganan Combine, atau permintaan jaringan, deinit harus membatalkannya. Jika tidak, kebocoran memori dan kelanjutan pekerjaan latar belakang setelah layar ditutup tidak dapat dihindari. Selalu gunakan Cancellable store Combine atau invalidate timer di deinit.
Pertanyaan yang Sering Diajukan
@StateObject ditambahkan di SwiftUI 2.0 pada WWDC 2020 bersama dengan iOS 14, macOS 11, watchOS 7, dan tvOS 14. Sebelumnya, @ObservedObject adalah satu-satunya cara bekerja dengan ObservableObject, yang menyebabkan seringnya bug kehilangan data.
Tidak, @StateObject tidak mendukung tipe Optional. Objek harus diinisialisasi saat deklarasi. Jika Anda membutuhkan objek opsional, gunakan @ObservedObject atau @EnvironmentObject dengan tipe opsional.
Tambahkan print(#function) di inisialisator dan deinit ObservableObject. Jika init tidak dipanggil saat render ulang — @StateObject berfungsi dengan benar. Jika init dipanggil setiap kali — ganti @ObservedObject dengan @StateObject.
Ya, @StateObject berfungsi di view SwiftUI yang disematkan di UIKit melalui UIHostingController. Siklus hidup objek terikat ke view SwiftUI, bukan ke UIViewController. Jika view SwiftUI diganti, @StateObject dihancurkan.
Beberapa @StateObject kecil dengan tanggung jawab terpisah. Ini meningkatkan kemampuan uji, penggunaan kembali, dan kinerja — saat satu objek berubah, hanya bagian antarmuka yang berlangganan yang digambar ulang, bukan seluruh view.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga