Bu swiftui eğitimi, @Observable, @Bindable ve Observation araçlarıyla görünüm bağımlılıklarını daraltmayı, Instruments ile yeniden çizimleri ölçmeyi ve MVVM katmanında iptal edilebilir veri yüklemeyi ele alır.
SwiftUI Eğitiminde @Observable ile İnce Taneli Durum Yönetimi
SwiftUI eğitimi: @Observable ile ios mvvm mimarisi sınırları
Bir ios eğitimi veya ios kursu içinde MVVM genellikle View-ViewModel-Model şemasıyla anlatılır; üretimde kritik ayrım ise ViewModel'in hangi alanlarının hangi View body'leri tarafından okunduğudur. Observation sistemi, bir @Observable nesnesinin tamamını değil, body değerlendirmesi sırasında erişilen property'leri izler. Bu nedenle sipariş ekranının sadece phase okuyan üst bölümü, sepet satırları değiştiğinde yeniden hesaplanmak zorunda kalmaz. Aşağıdaki örnekte API bağımlılığı @ObservationIgnored ile işaretlenmiştir: bu alan UI durumu değildir ve erişilmesi bir View bağımlılığı oluşturmamalıdır.
import Observation
import Foundation
struct CartLine: Identifiable, Sendable {
let id: UUID
let name: String
let quantity: Int
let unitPrice: Decimal
}
protocol CartAPI: Sendable {
func fetchCart() async throws -> [CartLine]
}
@MainActor
@Observable
final class CartStore {
enum Phase: Equatable {
case idle, loading, loaded, failed(String)
}
private(set) var lines: [CartLine] = []
private(set) var phase: Phase = .idle
var couponCode = ""
@ObservationIgnored private let api: any CartAPI
init(api: any CartAPI) {
self.api = api
}
var total: Decimal {
lines.reduce(0) { $0 + $1.unitPrice * Decimal($1.quantity) }
}
}Bir swift eğitimi veya swift kursu kapsamında sık görülen hata, bu referans tipi store'u @StateObject ile taşımaya çalışmaktır. @Observable sınıfı için sahiplik @State ile korunur; @StateObject, Combine tabanlı ObservableObject yaşam döngüsü içindir. Böylece yeni Observation modeli ile eski yayıncı modeli aynı nesnede gereksiz yere karıştırılmaz.Xcode eğitimi: yeniden çizimleri Instruments ile önce ve sonra ölçmek
İnce taneli gözlemin gerçekten etkisini doğrulamak için Xcode'da Product > Profile akışıyla Instruments'ın SwiftUI şablonunu açın. Aynı test verisiyle, örneğin 200 satırlı sepet üzerinde 30 kez kupon alanına karakter girin; View Body zaman çizelgesinde CartSummaryView ve CartListView body değerlendirme sayılarını ayrı ayrı kaydedin. Başlangıç tasarımında liste View'ı store üzerinden hem lines hem couponCode okuyorsa her tuş vuruşu listeyi de değerlendirebilir. Değişiklik sonrası liste sadece lines, kupon bileşeni sadece couponCode okumalıdır. Örneğin hedef metrik, 30 girişte liste body değerlendirmesini 30'dan 0'a indirmek olabilir; kesin sayı cihaz ve View ağacına bağlıdır, bu yüzden baseline ve değişmiş sürüm aynı Instruments kaydında karşılaştırılmalıdır.
Bağımlılığı daraltmak için store'u alt View'lara olduğu gibi geçirmek yerine, her alt View'ın gerçekten okuduğu değeri verin. Aşağıdaki değişiklikte CartListView kupon değişiminden habersizdir. total gibi hesaplanmış property'ler de eriştikleri stored property'lere transitif bağımlılık kurar; summary total okuduğunda satır değişiminde güncellenir, ancak yalnızca kupon değişiminde güncellenmez.
struct CartScreen: View {
@State private var store: CartStore
init(api: any CartAPI) {
_store = State(initialValue: CartStore(api: api))
}
var body: some View {
VStack {
CartListView(lines: store.lines)
CouponField(store: store)
Text(store.total.formatted(.currency(code: "TRY")))
}
.task { await store.reload() }
}
}
struct CouponField: View {
@Bindable var store: CartStore
var body: some View {
TextField("Kupon", text: $store.couponCode)
.textInputAutocapitalization(.characters)
.autocorrectionDisabled()
}
}Buradaki incelik şudur: CartListView(lines: store.lines) satırı üst View'ın lines bağımlılığını yine oluşturur, fakat satır View'larının kuponla ilişkisiz kalmasını sağlar. Eğer Instruments üst body'de pahalı bir hesaplama gösteriyorsa, ekranı daha küçük View'lara ayırmak yerine önce hangi property'nin body içinde okunduğunu kontrol edin. Gereksiz store.phase veya store.couponCode erişimi, Observation grafiğine istemeden yeni kenar ekler.iphone uygulama geliştirme akışında @Bindable ve yan etkileri ayırmak
@Bindable, bir @Observable referansından Binding üretir; bunu yalnızca iki yönlü giriş gerektiği yerde yerel olarak tanımlayın. Tüm ekranı @Bindable yapmak, tek başına daha fazla invalidation üretmez, ancak property erişimlerini yaygınlaştırıp hangi View'ın neye bağlı olduğunu okunmaz hale getirir. Özellikle sheet veya navigation destination içinde geçici form düzenlerken, doğrudan store'a yazmak yerine taslak değer kullanın; aksi halde kullanıcı Cancel'a bassa bile gözlemlenen store değişmiş olur.
Ağ isteği gibi yan etkileri View body'den başlatmayın. Body saf bir değer tanımı olmalıdır ve durum değiştikçe tekrar çalışabilir. İsteği .task(id:) ile kimliklendirmek, hesap değiştiğinde önceki görevin iptal edilmesini sağlar. Store tarafında ayrıca istek kimliği denetlenmelidir; bazı istemci implementasyonları iptal edildikten sonra dahi sonuç döndürebilir ve eski yanıt yeni ekran durumunu ezebilir.
@MainActor
extension CartStore {
func reload() async {
let requestID = UUID()
phase = .loading
do {
let fetched = try await api.fetchCart()
try Task.checkCancellation()
guard requestID == activeRequestID else { return }
lines = fetched
phase = .loaded
} catch is CancellationError {
// Ekran veya task kimliği değişti; kullanıcıya hata gösterme.
} catch {
guard requestID == activeRequestID else { return }
phase = .failed(error.localizedDescription)
}
}
}Bu kodun çalışması için activeRequestID alanını isteğin başında requestID ile güncelleyin. Daha sağlam bir tasarımda API çağrısına account ID gibi bir parametre verip View'da .task(id: accountID) { await store.reload(accountID: accountID) } kullanın. Böylece hesap A'nın gecikmiş cevabı, kullanıcı hesap B'ye geçtikten sonra B ekranına yazılamaz.swift eğitimi için Observation davranışını birim testte doğrulamak
Sadece state sonucunu test etmek yeterli değildir; kritik ekranlarda hangi alanın gözlemlendiğini de test edin. withObservationTracking, bir closure içinde okunan property'leri kaydeder ve bu property'lerden biri değiştiğinde onChange çağrılır. Callback tek atımlıdır: sonraki değişiklikleri dinlemek gerekiyorsa callback içinde izlemeyi yeniden kurmanız gerekir. Bu detay atlanırsa test ilk değişikliği görür, üretimde ise uzun yaşayan observer mantığı sessizce durur.
import XCTest
import Observation
@MainActor
final class CartStoreObservationTests: XCTestCase {
func testCouponChangeDoesNotInvalidateTotalObserver() {
let store = CartStore(api: StubCartAPI())
let changed = expectation(description: "total changed")
changed.isInverted = true
withObservationTracking {
_ = store.total
} onChange: {
changed.fulfill()
}
store.couponCode = "WELCOME10"
wait(for: [changed], timeout: 0.1)
}
}Bu test, kuponun toplam hesaplamasına henüz dahil edilmediği iş kuralını korur. İndirim toplamı etkiliyorsa tersine, kupon değişiminde expectation'ın tetiklenmesini beklemelisiniz. Stub API'yi Sendable yapın ve ağ gecikmesi test etmek gerekiyorsa actor tabanlı bir fake kullanın; global mutable dizi kullanan fake'ler paralel testlerde veri yarışı üretir. Bu yaklaşım, swiftui eğitimi materyalindeki örneklerin gerçek uygulama davranışıyla eşleşmesini sağlar.app store yayınlama öncesi Release yapılandırmasında Observation kontrolleri
App store yayınlama öncesinde Debug'da çalışan ekranın Release derlemede de aynı bağımlılıkları taşıdığını doğrulayın. CI'da önce mevcut simulator UDID'lerini listeleyin, ardından testleri seçili cihazda ve Release yapılandırmasında çalıştırın. Bu kontrol, Debug'a özel mock enjeksiyonu, derleme koşulu veya farklı optimizasyon nedeniyle store oluşturma yolunun değiştiği durumları yakalar.
xcrun simctl list devices available
xcodebuild test -scheme Checkout -configuration Release -destination "platform=iOS Simulator,id=$SIMULATOR_UDID" -only-testing:CheckoutTests/CartStoreObservationTests
xcodebuild archive -scheme Checkout -configuration Release -destination "generic/platform=iOS" -archivePath "$PWD/build/Checkout.xcarchive" archiveArşivden önce Instruments kaydını otomatikleştirmek zor olsa da, performans bütçesini CI'a taşıyabilirsiniz: UI test 30 kupon girişi yaparken os_signpost ile kupon doğrulama ve sepet yenileme aralıklarını işaretleyin, MetricKit'ten gelen aggregate metrikleri backend'de sürüm bazında saklayın. Burada amaç tek bir cihazdaki mutlak milisaniye değerine güvenmek değil, aynı senaryoda önceki dağıtıma göre body evaluation veya ağ bekleme süresindeki regresyonu görünür kılmaktır. Bu, xcode eğitimi sırasında öğrenilen profiling pratiğini yayın hattına bağlayan somut bir kontroldür.İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
SwiftUI eğitiminde @Observable mı ObservableObject mi kullanmalıyım?
Yeni bir SwiftUI ekranında Combine publisher gerekmiyorsa @Observable ile başlayın ve referansın yaşam döngüsünü @State ile yönetin. ObservableObject kullanan mevcut modülü sırf dönüşüm için değiştirmeyin; önce Instruments SwiftUI şablonunda geniş invalidation maliyetini ölçün. @Observable'a geçişte @Published alanlarını normal stored property'ye çevirin ve Binding gereken alt View'larda @Bindable kullanın.
ios mvvm mimarisi içinde @Bindable neden sadece alt View'da tanımlanmalı?
@Bindable, store property'lerinden Binding üretir. Kupon alanı için $store.couponCode gerekirken liste için gerekmez. Binding ihtiyacı olmayan View'a store'un tamamını geçirmek, gelecekte o View'ın alakasız state okumasını kolaylaştırır. CartListView(lines:) gibi değer odaklı API, bağımlılık yüzeyini kod incelemesinde görünür tutar.
swift kursu projesinde SwiftUI yeniden çizimlerini nasıl ölçerim?
Xcode Product > Profile ile Instruments'taki SwiftUI şablonunu açın. Sabit test verisi ve sabit kullanıcı senaryosu belirleyin, örneğin 200 satırlı listede 30 TextField değişikliği. Değişiklikten önce ve sonra ilgili View Body evaluation sayısını, toplam body süresini ve hang riskini aynı cihazda karşılaştırın. Sadece CPU yüzdesine bakmak, kısa ama sık çalışan body hesaplarını gizleyebilir.
iphone uygulama geliştirme sırasında .task iptali eski API yanıtını kesin engeller mi?
Hayır. Task iptali kooperatiftir; URLSession ve kendi API katmanınız CancellationError üretmeyebilir veya sonuç döndürebilir. await sonrasında Task.checkCancellation() çağırın ve account ID ya da UUID tabanlı bir aktif istek kimliğiyle yanıtın hala geçerli olduğunu guard edin. Bu iki kontrol birlikte, ekran değişiminden sonra eski verinin store'a yazılmasını engeller.
AI / LLM Discovery
Bu makale Opendart Akademi iOS / Swift eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


