@StateObject, SwiftUI’da bir ObservableObject örneğini doğrudan bir view içinde oluşturmak ve sahiplenmek için kullanılan bir Property Wrapper’dır. SwiftUI, nesnenin view’ın yaşam döngüsü boyunca bir kez başlatılmasını ve sonraki render işlemlerinde yeniden oluşturulmamasını garanti eder. Apple Developer Documentation (2025)’a göre, @StateObject veri kaynağı oluşturan kök view’lar için önerilir. @StateObject, SwiftUI hiyerarşisinde bir ObservableObject’e sahip olmak için doğru seçimdir.
Önemli Noktalar
@StateObject, SwiftUI 2.0’da (iOS 14) tanıtılan ve @ObservedObject ile @State’in yeteneklerini birleştiren bir Property Wrapper’dır. @ObservedObject gibi, ObservableObject değişikliklerine abone olur. @State gibi, verilerin tekrarlanan view yapısı başlatmalarından kurtulmasını garanti eder. @StateObject, view ilk kez ekrana geldiğinde nesneyi bir kez oluşturur ve SwiftUI’nın heap’inde saklar.
@StateObject’ten önce, geliştiriciler view’larda oluşturulanlar da dahil olmak üzere tüm ObservableObject’ler için @ObservedObject kullanıyordu. Bu, üst view güncellendiğinde sık veri kaybına yol açıyor, view yapısı yeniden oluşturuluyor ve @ObservedObject örneği de beraberinde gidiyordu. @StateObject, kararlılık garantisi ekleyerek bu sorunu çözdü.
Temel kural: @StateObject, varsayılan başlatıcıda (let model = ViewModel()) nesneyi oluşturan view’da kullanılır. Bu nesneyi alan alt view’lar @ObservedObject kullanır. Bu ayrım, tüm hiyerarşi boyunca tek bir gerçek kaynağı garanti eder.
SwiftUI, @StateObject’in yaşam döngüsünü @State’e benzer bir depolama yöneticisi aracılığıyla yönetir. View ilk kez göründüğünde, SwiftUI nesne için bellek ayırır ve kalıcı bir alanda saklar. Sonraki render işlemlerinde (body çağrıları), nesne yeniden oluşturulmaz—mevcut örnek kullanılır. Nesne, view hiyerarşide olduğu sürece yaşar.
View hiyerarşiden kaldırıldığında, SwiftUI @StateObject’i yok eder ve deinit çağırır. View hiyerarşiye tekrar eklendiğinde yeni bir örnek oluşturulur. Tasarım yaparken bunu dikkate almak önemlidir: view kaldırmaları arasında veriyi korumanız gerekiyorsa, kalıcılık için bir hizmet katmanı (singleton veya DI) veya @AppStorage kullanın.
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() }
}
}
Örnekte, TimerViewModel @StateObject aracılığıyla oluşturulur ve TimerView ekranda olduğu sürece yaşar. Zamanlayıcı onAppear’da başlar ve deinit’te durur. @ObservedObject kullanılsaydı, TimerView’ın her render işlemi saniye = 0 olan yeni bir TimerViewModel oluşturur ve zamanlayıcı asla doğru çalışmazdı. @StateObject, viewModel’in benzersiz ve kararlı olmasını garanti eder.
@StateObject ve @ObservedObject arasındaki seçim, nesnenin sahibinin kim olduğuna bağlıdır. View nesneyi oluşturuyorsa—@StateObject. View hazır bir nesne alıyorsa—@ObservedObject. Bu kural o kadar önemlidir ki Xcode, bir başlatıcı aracılığıyla nesne alan alt view’da @StateObject kullanıldığında bir uyarı gösterir.
| Durum | Önerilen Wrapper |
|---|---|
| View, ViewModel() ile model oluşturur | @StateObject |
| View, modeli üstten alır | @ObservedObject |
| Model tek bir view’da kullanılır | @StateObject |
| Model Environment aracılığıyla iletilir | @EnvironmentObject |
| Önizlemeler için model gerekir | @ObservedObject + mock |
Pratikte, bir projenin başında @StateObject genellikle kök view’da ve @ObservedObject tüm alt view’larda kullanılır. Uygulama büyüdükçe, hiyerarşiyi basitleştirmek için bazı @StateObject örnekleri @EnvironmentObject ile değiştirilebilir. Ancak @StateObject, kendi mantığına sahip modüler ekranlar için en iyi seçim olmaya devam eder.
İlk kalıp—@StateObject ile MVVM. ObservableObject olarak ViewModel, @StateObject aracılığıyla view’da oluşturulur. ViewModel, @Published özellikleri ve iş mantığını içerir. View, değişikliklere abone olur ve arayüzü günceller. Bu yaklaşım, test edilebilir yalıtım sağlar: ViewModel, doğrudan bir örnek oluşturarak UI olmadan test edilebilir.
İkinci kalıp—Bağımlılıklarla @StateObject. ViewModel hizmet gerektiriyorsa, parametrelerle başlatma kullanın. Örneğin, @StateObject var viewModel = UserViewModel(api: APIClient.shared). Ancak dikkatli olun: parametreler her body renderında hesaplanır, ancak nesne yalnızca bir kez oluşturulur. SwiftUI, @StateObject’in sonraki başlatmalarını yok sayar.
Üçüncü kalıp—iç içe @StateObject’ler. SwiftUI’da bir view’da birden çok @StateObject bulunabilir, ancak bu nadiren haklıdır. Genellikle bir @StateObject, view’ın tüm veri kümesini yönetir. Mantık çok karmaşık hale gelirse, onu bir @StateObject içindeki @ObservedObject hizmetlerinin bir bileşimine bölün.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
Örnekte, AppView iki @StateObject oluşturur: gezinmeyi yönetmek için NavigationRouter ve kimlik doğrulama için AuthViewModel. Her iki nesne de environmentObject aracılığıyla Environment’a enjekte edilir. Herhangi bir alt view, başlatıcı zincirinden geçmeden @EnvironmentObject aracılığıyla bunlara erişebilir.
@StateObject, herhangi bir parametreyle başlatmayı destekler, ancak önemli bir uyarıyla: başlatıcı yalnızca bir kez çağrılır. Sonraki body render işlemlerinde, parametrelerdeki yeni değerler yok sayılır. Bu, @State var id: Int = 5’i @StateObject var vm = ViewModel(id: id)’ye aktarırsanız, id değiştiğinde ViewModel’in yeni değeri almayacağı anlamına gelir.
Bu sorunu çözmek için senkronizasyon için onReceive veya onAppear kullanın. Combine aracılığıyla ViewModel içinde parametre değişikliklerine abone olun veya parametreleri view düzeyinde .onChange(of:) yöntemiyle iletin. Nesnenin harici değişikliklere dinamik olarak yanıt vermesi gerekiyorsa, @StateObject yerine @ObservedObject kullanmak bir alternatiftir.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
Doğru yaklaşım: DetailView, itemId’yi bir let özelliği olarak alır (yapının başlatıcısı aracılığıyla iletilir) ve @StateObject, parametresiz DetailViewModel oluşturur. onAppear’da, iletilen ID için veri yüklemek üzere load(id:) yöntemi çağrılır. Bu, ViewModel’in @StateObject mekanizması tarafından oluşturulmasını, ancak verilerin geçerli ID ile her view görünümünde yüklenmesini garanti eder.
Ana hata—nesneyi bir üstten alan alt view’larda @StateObject kullanmak. ParentView @StateObject model oluşturur ve ChildView @StateObject var model: ModelType (varsayılan parametreyle) bildirirse, ChildView kendi bağımsız örneğini oluşturur. Üst ve alt nesneler bağlantılı olmaz ve birindeki değişiklikler diğerine yansımaz.
İkinci hata—List veya ForEach içinde @StateObject kullanmak. Listenin her öğesi kendi @StateObject’ini oluşturur ve bu da birden çok bağımsız örneğe yol açar. Listeler için doğru yaklaşım, tüm öğelere @ObservedObject aracılığıyla tek bir ObservableObject iletmek veya List içinde @State ile Identifiable yapıları kullanmaktır.
Üçüncü sorun—deinit temizliği eksikliği. @StateObject, view’ın tüm yaşam döngüsü boyunca yaşar. Nesne zamanlayıcılar, Combine abonelikleri veya ağ istekleri oluşturursa, deinit bunları iptal etmelidir. Aksi takdirde, bellek sızıntıları ve ekran kapatıldıktan sonra arka plan çalışmasının devam etmesi kaçınılmazdır. Her zaman bir Combine Cancellable deposu kullanın veya deinit içinde zamanlayıcıları geçersiz kılın.
Sıkça Sorulan Sorular
@StateObject, WWDC 2020’de SwiftUI 2.0’a iOS 14, macOS 11, watchOS 7 ve tvOS 14 ile birlikte eklendi. Bundan önce, @ObservedObject ObservableObject ile çalışmanın tek yoluydu ve bu da sık sık veri kaybı hatalarına yol açıyordu.
Hayır, @StateObject Optional türlerini desteklemez. Nesne bildirim sırasında başlatılmalıdır. İsteğe bağlı bir nesneye ihtiyacınız varsa, isteğe bağlı bir türle @ObservedObject veya @EnvironmentObject kullanın.
ObservableObject’in başlatıcısına ve deinit’ine print(#function) ekleyin. Render işlemlerinde init çağrılmazsa—@StateObject doğru çalışıyordur. Init her çağrıldığında—@ObservedObject’i @StateObject ile değiştirin.
Evet, @StateObject, UIHostingController aracılığıyla UIKit’e gömülü SwiftUI view’larında çalışır. Nesne yaşam döngüsü, UIViewController’a değil, SwiftUI view’ına bağlıdır. SwiftUI view değiştirilirse, @StateObject yok edilir.
Ayrı sorumluluklara sahip birkaç küçük @StateObject daha iyidir. Bu, test edilebilirliği, yeniden kullanılabilirliği ve performansı artırır—bir nesne değiştiğinde, tüm view değil, arayüzün yalnızca abone olunan kısımları yeniden çizilir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun