iOS 17 ile gelen Observation framework, SwiftUI'da state management'i koklunden degistirdi. @Observable macro sayesinde artik @Published, @StateObject ve @ObservedObject gibi wrapper'lara ihtiyac kalmadi. Bu rehberde Observation framework'un calisma mantigi, migration stratejileri ve performans avantajlarini kesfedeceksiniz.
Icindekiler
- Neden Observation Framework?
- Temel Kullanim
- ObservableObject vs @Observable Karsilastirma
- Migration Stratejisi
- Performans Avantajlari
- Gelismis Kaliplar
- Combine ile Birlikte Kullanim
- Test Stratejileri
- Best Practices
- Sonuc
1. Neden Observation Framework?
Eski ObservableObject yaklasiminda ciddi performans sorunlari vardi:
Problem: Gereksiz View Guncellemesi
swift
1// ESKI YAKLASIM — ObservableObject2class UserViewModel: ObservableObject {3 @Published var name: String = ""4 @Published var email: String = ""5 @Published var avatar: UIImage?6 @Published var settings: UserSettings = .default7 @Published var notifications: [Notification] = []8}9 10// Bu view SADECE name kullaniyor ama11// email, avatar, settings, notifications degistiginde de12// YENIDEN CIZILIYOR!13struct UserNameView: View {14 @ObservedObject var viewModel: UserViewModel15 16 var body: some View {17 Text(viewModel.name) // Sadece name kullaniliyor18 }19}2. Temel Kullanim
swift
1import Observation2 3// YENI YAKLASIM — @Observable4@Observable5class UserViewModel {6 var name: String = ""7 var email: String = ""8 var avatar: UIImage?9 var settings: UserSettings = .default10 var notifications: [Notification] = []11 12 // Computed property'ler de otomatik izlenir13 var displayName: String {14 name.isEmpty ? "Misafir" : name15 }16}17 18// Bu view SADECE name degistiginde yeniden cizilir!19struct UserNameView: View {20 var viewModel: UserViewModel // @ObservedObject gerekmez!21 22 var body: some View {23 Text(viewModel.displayName)24 }25}@State ile Kullanim
swift
1struct ContentView: View {2 @State private var viewModel = UserViewModel()3 4 var body: some View {5 VStack {6 TextField("Isim", text: $viewModel.name)7 TextField("Email", text: $viewModel.email)8 9 // Alt view'e sadece referans gecir10 UserNameView(viewModel: viewModel)11 UserEmailView(viewModel: viewModel)12 }13 }14}15 16// Sadece email degistiginde guncellenir17struct UserEmailView: View {18 var viewModel: UserViewModel19 20 var body: some View {21 Text(viewModel.email)22 }23}3. ObservableObject vs @Observable Karsilastirma
Ozellik | ObservableObject | @Observable |
|---|---|---|
**Import** | Combine | Observation |
**Property Wrapper** | @Published gerekli | Otomatik |
**View Guncelleme** | Tum property'ler | Sadece kullanilan |
**Binding** | $viewModel.prop | $viewModel.prop |
**Nesting** | Sorunlu | Sorunsuz |
**Performans** | O(n) guncelleme | O(1) guncelleme |
**Min. iOS** | 13+ | 17+ |
**View Kullanim** | @StateObject/@ObservedObject | @State veya plain |
Performans Farki Ornegi
swift
1// Senaryo: 100 property'li bir model2// ObservableObject: Herhangi bir property degisiminde TUM view'ler guncellenir3// @Observable: Sadece ilgili property'yi kullanan view'ler guncellenir4 5// ObservableObject — 1 degisiklik = 100 view guncelleme (worst case)6// @Observable — 1 degisiklik = 1 view guncelleme (best case)4. Migration Stratejisi
Adim 1: Import Degistir
swift
1// Oncesi2import Combine3 4class SettingsStore: ObservableObject {5 @Published var theme: Theme = .light6 @Published var fontSize: CGFloat = 167 @Published var language: String = "tr"8}9 10// Sonrasi11import Observation12 13@Observable14class SettingsStore {15 var theme: Theme = .light16 var fontSize: CGFloat = 1617 var language: String = "tr"18}Adim 2: View Wrapper'lari Temizle
swift
1// Oncesi2struct SettingsView: View {3 @StateObject private var store = SettingsStore()4 5 var body: some View {6 ChildView(store: store)7 }8}9 10struct ChildView: View {11 @ObservedObject var store: SettingsStore12 // ...13}14 15// Sonrasi16struct SettingsView: View {17 @State private var store = SettingsStore()18 19 var body: some View {20 ChildView(store: store)21 }22}23 24struct ChildView: View {25 var store: SettingsStore // Plain property!26 // ...27}Adim 3: Environment Gecisi
swift
1// Oncesi2struct ParentView: View {3 @StateObject private var store = SettingsStore()4 var body: some View {5 ChildView()6 .environmentObject(store)7 }8}9 10struct ChildView: View {11 @EnvironmentObject var store: SettingsStore12 // ...13}14 15// Sonrasi16struct ParentView: View {17 @State private var store = SettingsStore()18 var body: some View {19 ChildView()20 .environment(store)21 }22}23 24struct ChildView: View {25 @Environment(SettingsStore.self) private var store26 // ...27}5. Performans Avantajlari
Observation framework property-level tracking yapar. SwiftUI, view'in body'sinde hangi property'lere erisildigini izler ve sadece o property'ler degistiginde view'i yeniden cizer.
swift
1@Observable2class DashboardData {3 var temperature: Double = 0 // Sensor A4 var humidity: Double = 0 // Sensor B5 var pressure: Double = 0 // Sensor C6 var windSpeed: Double = 0 // Sensor D7 var lastUpdate: Date = .now // Her saniye guncellenir8}9 10// Sadece temperature degistiginde guncellenir11struct TemperatureWidget: View {12 var data: DashboardData13 var body: some View {14 Text("\(data.temperature, specifier: "%.1f")°C")15 }16}17 18// Sadece humidity degistiginde guncellenir19struct HumidityWidget: View {20 var data: DashboardData21 var body: some View {22 Text("\(data.humidity, specifier: "%.0f")%")23 }24}25// lastUpdate her saniye degisse bile bu widget'lar etkilenmez!6. Gelismis Kaliplar
withObservationTracking
swift
1import Observation2 3@Observable4class Counter {5 var count = 06}7 8let counter = Counter()9 10// Manuel observation tracking11withObservationTracking {12 // Bu blokta erisilen property'ler izlenir13 print("Count: \(counter.count)")14} onChange: {15 // counter.count degistiginde cagirilir (bir kez!)16 print("Count degisti!")17}18 19counter.count = 42 // "Count degisti!" yazdirir20counter.count = 43 // Artik izlenmiyor (tek seferlik)@ObservationIgnored
swift
1@Observable2class CacheManager {3 var items: [String: Any] = [:]4 5 @ObservationIgnored6 var internalCache: NSCache<NSString, AnyObject> = .init()7 // internalCache degisiklikleri view guncellemesine neden olmaz8 9 @ObservationIgnored10 private var cancellables = Set<AnyCancellable>()11}7. Combine ile Birlikte Kullanim
swift
1import Observation2import Combine3 4@Observable5class SearchViewModel {6 var query: String = ""7 var results: [SearchResult] = []8 var isLoading = false9 10 @ObservationIgnored11 private var cancellables = Set<AnyCancellable>()12 13 init() {14 // Combine pipeline hala kullanilabilir15 setupSearchDebounce()16 }17 18 private func setupSearchDebounce() {19 // Timer-based debounce icin Combine hala faydali20 Timer.publish(every: 0.3, on: .main, in: .common)21 .autoconnect()22 .sink { [weak self] _ in23 guard let self = self else { return }24 Task {25 await self.performSearch()26 }27 }28 .store(in: &cancellables)29 }30 31 private func performSearch() async {32 guard !query.isEmpty else {33 results = []34 return35 }36 isLoading = true37 defer { isLoading = false }38 // API cagrisi...39 }40}8. Test Stratejileri
swift
1import Testing2import Observation3 4@Suite("UserViewModel Tests")5struct UserViewModelTests {6 7 @Test("Name degisikligi displayName'i gunceller")8 func nameUpdatesDisplayName() {9 let vm = UserViewModel()10 vm.name = "Ahmet"11 #expect(vm.displayName == "Ahmet")12 }13 14 @Test("Bos isim 'Misafir' dondurur")15 func emptyNameReturnsMisafir() {16 let vm = UserViewModel()17 vm.name = ""18 #expect(vm.displayName == "Misafir")19 }20 21 @Test("Observation tracking calisiyor")22 func observationTracking() async {23 let vm = UserViewModel()24 var changed = false25 26 withObservationTracking {27 _ = vm.name28 } onChange: {29 changed = true30 }31 32 vm.name = "Test"33 #expect(changed == true)34 }35}9. Best Practices
- Yeni projeler icin @Observable kullanin — iOS 17+ hedefliyorsaniz
- @ObservationIgnored ile gereksiz tracking'i onleyin — cache, cancellables
- Computed property'leri kullanin — otomatik izlenir
- Nesting'i korkmadan kullanin — ic ice @Observable nesneler sorunsuz
- Environment ile dagitin — .environment(store) kullanin
- Combine'i tamamen birakmayin — debounce, merge gibi reactive islemler icin hala faydali
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Tebrikler! Bu yaziyi sonuna kadar okudugun icin sana ozel bir hediyem var:
10. Sonuc
Observation framework, SwiftUI state management icin buyuk bir adim. Property-level tracking ile performansi artirirken, basitlestirrilmis API ile gelistirici deneyimini de iyilestiriyor. iOS 17+ hedefleyen tum projelerde @Observable'a gecis yapmanizi siddetle oneriyoruz.
Sonuc ve Oneriler
Observation framework, SwiftUI'nin state management yaklasiminda bir paradigma degisikligini temsil eder. ObservableObject ve @Published ile calisan mevcut yapilarda her property degisikligi tum subscriber'lari tetiklerken, @Observable macro'su property-level granularity ile yalnizca gercekten degisen degerri kullanan view'lari gunceller. Bu fark, ozellikle karmasik veri modelleri olan uygulamalarda dramatik performans iyilesmesi saglar.
Migration sureci planlarken once en cok view guncelleme tetikleyen model siniflarinizi belirleyin ve bunlari @Observable'a donusturun. @ObservationIgnored ile izlenmemesi gereken property'leri isaretlemeyi unutmayin. withObservationTracking fonksiyonu ile UIKit entegrasyonu yaparak hibrit projelerde de Observation'dan faydalanabilirsiniz.
Test yazarken @Observable siniflarin davranisini dogrulamak icin XCTest ile birlikte property degisikliklerini takip eden yardimci fonksiyonlar olusturun. Bu yaklasim, hem state yonetiminin dogrulugunu garanti eder hem de gelecekteki refactoring'lerde guvenlik agi saglar.

