SwiftUI uygulamalarında tip güvenli feature flag, ETag tabanlı yenileme, MVVM entegrasyonu, deterministik test ve App Store yayınlama sırasında geri alma planını uygulanabilir Swift örnekleriyle ele alın.
SwiftUI Eğitiminde Feature Flag Tasarımı ve Güvenli Yayınlama
SwiftUI eğitimi için feature flag sözleşmesini tip güvenli kurmak
Feature flag'i doğrudan UserDefaults.bool(forKey:) ile okumak, yazım hatasını sessizce false sonucuna indirger ve kullanılmayan bayrakların izini kaybettirir. Bunun yerine bayrak adlarını tek bir enum altında toplayın; uzaktan gelen JSON'da bilinmeyen anahtarları da loglayın. Bu yaklaşım, ios eğitimi ve swift eğitimi kapsamında özellikle istemci ile backend'in farklı deploy edildiği ekiplerde önemlidir: backend yeni bir anahtar gönderdiğinde eski istemci onu yok sayabilir, ancak var olan bir anahtarın anlamı geriye dönük uyumlu kalmalıdır.
enum Feature: String, CaseIterable, Sendable {
case semanticSearch
case compactCheckout
case maintenanceMode
}
struct FlagSnapshot: Codable, Sendable {
let revision: Int
let values: [String: Bool]
func enabled(_ feature: Feature) -> Bool {
values[feature.rawValue] ?? false
}
var unknownKeys: [String] {
values.keys.filter { Feature(rawValue: $0) == nil }
}
}Flag değerini istemcide yetkilendirme mekanizması olarak kullanmayın. Örneğin maintenanceMode yalnızca ekranı kapatabilir; ödeme iadesi, rol kontrolü veya premium içerik erişimi için sunucunun her istekte yetki doğrulaması gerekir. Çünkü uygulama paketi tersine mühendislikle değiştirilebilir ve HTTPS üzerinden gelen flag yanıtı cihazdaki güven sınırını değiştirmez. Sözleşmeye revision eklemek ayrıca crash ve analitik olaylarına hangi konfigürasyonla gidildiğini yazmayı mümkün kılar: olay yüküne flag_revision=42 ekleyin.
iOS MVVM mimarisi içinde ETag ve actor ile flag yenileme
Aynı flag endpoint'ine birden fazla SwiftUI ekranının .task ile gitmesi, gereksiz istek ve yarış durumu üretir. Durumu bir actor içinde tutun; actor'ın seri erişimi ETag ve snapshot güncellemesini tek kritik bölgeye alır. HTTP 304 yanıtında gövde gelmediği için mevcut snapshot'ı korumak zorundasınız. Yaygın hata, 304'ü JSON decode etmeye çalışıp uygulamayı boş konfigürasyonla başlatmaktır.
actor FeatureFlagStore {
private var snapshot = FlagSnapshot(revision: 0, values: [:])
private var eTag: String?
private let endpoint = URL(string: "https://config.example.com/ios/flags")!
func refresh(session: URLSession = .shared) async throws {
var request = URLRequest(url: endpoint)
request.setValue("application/json", forHTTPHeaderField: "Accept")
if let eTag {
request.setValue(eTag, forHTTPHeaderField: "If-None-Match")
}
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
switch http.statusCode {
case 200:
snapshot = try JSONDecoder().decode(FlagSnapshot.self, from: data)
eTag = http.value(forHTTPHeaderField: "ETag")
case 304:
break
default:
throw URLError(.badServerResponse)
}
}
func isEnabled(_ feature: Feature) -> Bool {
snapshot.enabled(feature)
}
}MVVM katmanında ViewModel, sözlüğü veya HTTP ayrıntısını görmemeli; yalnızca ürün davranışını temsil eden bir Boolean yayınlamalıdır. @MainActor ViewModel'in gözlemlenen durumunun ana aktörde değişmesini zorunlu kılar. SwiftUI eğitimi sırasında sık görülen hata, .task içinde doğrudan actor'dan dönen değeri view'a dağıtmaktır; bu, aynı kararın birden fazla view tarafından tekrar hesaplanmasına ve test yüzeyinin büyümesine neden olur.
@MainActor
@Observable
final class SearchViewModel {
var semanticSearchEnabled = false
private let flags: FeatureFlagStore
init(flags: FeatureFlagStore) {
self.flags = flags
}
func loadFlags() async {
semanticSearchEnabled = await flags.isEnabled(.semanticSearch)
}
}
struct SearchView: View {
@State private var model: SearchViewModel
var body: some View {
Group {
if model.semanticSearchEnabled {
SemanticSearchScreen()
} else {
ClassicSearchScreen()
}
}
.task { await model.loadFlags() }
}
}Swift kursu projelerinde bağımlılık enjeksiyonu ve deterministik test
Testte gerçek URLSession kullanmak zaman aşımı, DNS ve sunucu konfigürasyonu yüzünden sonucu belirsizleştirir. FeatureFlagStore için HTTP taşımasını küçük bir protokolle enjekte edin; üretimde URLSession adaptörü, testte bellek içi bir stub kullanın. Bu tasarım bir swift kursu örneğinde basit görünse de pratikte 200, 304 ve 500 geçişlerini ağ olmadan doğrulamanızı sağlar.
protocol FlagTransport: Sendable {
func load(eTag: String?) async throws -> (status: Int, data: Data, eTag: String?)
}
struct StubTransport: FlagTransport {
let response: (status: Int, data: Data, eTag: String?)
func load(eTag: String?) async throws -> (status: Int, data: Data, eTag: String?) {
response
}
}
@Test("304 yaniti son bilinen snapshot'i korur")
func notModifiedKeepsSnapshot() async throws {
let json = Data("{\"revision\":7,\"values\":{\"semanticSearch\":true}}".utf8)
let first = StubTransport(response: (200, json, "v7"))
// Uretimde store'a iki farkli transport vermek yerine
// transport'u degistirilebilir test factory ile kurun.
#expect(first.response.status == 200)
}Gerçek test paketi için Xcode'da Test Plan oluşturun ve flag testlerini ayrı bir gruba koyun. CI'da yalnızca ilgili grubu çalıştırmak için xcodebuild test -scheme MyApp -testPlan FeatureFlags -destination 'platform=iOS Simulator,name=iPhone 16' komutunu kullanın. xcode eğitimi bağlamında kritik ayrıntı şudur: URLProtocol.registerClass süreç geneline etki eder; paralel testlerde başka URLSession çağrılarını yanlışlıkla yakalayabilir. Bu nedenle URLProtocol kullanacaksanız global kayıt yerine yalnızca enjekte edilmiş URLSessionConfiguration.protocolClasses üzerinde kullanın.
iPhone uygulama geliştirme sürecinde SwiftUI Environment sınırı
Tek bir global singleton ile flag okumak Preview, UI test ve çoklu hesap oturumlarında sızıntı üretir. Uygulama kökünde store'u oluşturup Environment üzerinden verin; Preview'da ise sabit snapshot döndüren ayrı bir store enjekte edin. Böylece ekranın görünümü, uzaktaki konfigürasyon servisinin ayakta olmasına bağlı olmaz. ios mvvm mimarisi açısından Environment yalnızca bağımlılığı taşır, iş kuralı yine ViewModel'de kalır.
private struct FeatureFlagStoreKey: EnvironmentKey {
static let defaultValue = FeatureFlagStore()
}
extension EnvironmentValues {
var featureFlags: FeatureFlagStore {
get { self[FeatureFlagStoreKey.self] }
set { self[FeatureFlagStoreKey.self] = newValue }
}
}
@main
struct MyApp: App {
private let flags = FeatureFlagStore()
var body: some Scene {
WindowGroup {
RootView()
.environment(\.featureFlags, flags)
}
}
}Hesap değişiminde flag cache'ini bilinçli olarak ayırın. Bayraklar kullanıcı segmentine göre dönüyorsa yalnızca ETag saklamak yetersizdir; cache anahtarına kullanıcı kimliği yerine hash'lenmiş segment sürümü ekleyin ve logout'ta o segmentin disk kaydını silin. Örneğin flags.enterprise.v3 ile flags.consumer.v3 ayrı tutulabilir. Ham e-posta veya kullanıcı kimliğini UserDefaults anahtarına koymak, cihaz yedeği ve debug çıktılarında gereksiz kişisel veri izi bırakır.
App Store yayınlama öncesi gözlemlenebilirlik ve geri alma planı
app store yayınlama öncesinde bir flag'in gerçekten kaç cihazda aktif olduğunu sadece uzaktan konfigürasyon panelinden tahmin etmeyin. İstemcide snapshot revision, HTTP durum kodu ve yenileme süresini OSLog ile kaydedin. Xcode Instruments içindeki Points of Interest şablonunda aynı kullanıcı akışını iki kez ölçün: önce koşulsuz 200 gövdeli istekle, sonra If-None-Match ile. Karşılaştırmada ortalama değil P95 yenileme süresini, 304 oranını ve indirilen byte sayısını raporlayın; 304 yanıtı gövde taşımadığı için radyo kullanımını doğrudan azaltır.
import os
let flagLog = OSLog(subsystem: "com.example.myapp", category: "feature-flags")
let signpostID = OSSignpostID(log: flagLog)
os_signpost(.begin, log: flagLog, name: "FlagRefresh", signpostID: signpostID)
// await store.refresh() cagrisi burada yapilir.
os_signpost(.end, log: flagLog, name: "FlagRefresh", signpostID: signpostID)
os_log("Flag snapshot revision=%{public}d", log: flagLog, type: .info, 42)Geri alma stratejisini iki ayrı bayrakla tasarlayın: yeni arayüzü açan compactCheckout ve kritik akışı tamamen kapatan maintenanceMode. İkinci bayrak için uygulama açılışında diskten son bilinen değeri senkron okuyun, ağ yenilemesini sonra başlatın; aksi halde servis erişilemezken kill switch etkisiz kalır. Bu ayrım, iphone uygulama geliştirme ekibinin onay beklemeden riskli bir akışı kapatmasını sağlar. Ayrıca Apple incelemesi için davranışı gizleyen değil, uygulamanın temel işlevini bozmadan sınırlayan bir fallback ekranı ve destek bağlantısı hazırlayın.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
SwiftUI eğitiminde feature flag için UserDefaults yeterli mi?
Yalnızca uygulama içi, statik ve kullanıcı segmentinden bağımsız tercih için yeterli olabilir. Uzaktan flag kullanıyorsanız tipli Feature enum'u, revision alanı, ETag saklama ve 304 yanıtında mevcut snapshot'ı koruyan bir store kurun. UserDefaults'a yalnızca doğrulanmış son snapshot'ı yazın.
iOS MVVM mimarisi içinde feature flag View'a mı ViewModel'e mi konulmalı?
HTTP isteği ve flag anahtarı ViewModel veya ayrı bir use-case katmanında kalmalı; View yalnızca semanticSearchEnabled gibi sunuma dönük durumu okumalıdır. Bu sayede aynı ViewModel'i stub FlagTransport ile test edebilir, SwiftUI Preview'da ağ erişimi olmadan farklı durumları gösterebilirsiniz.
Swift kursu projelerinde remote config testi nasıl yapılır?
URLSession'ı doğrudan çağırmak yerine FlagTransport benzeri Sendable bir protokol enjekte edin. Testte 200 yanıtı ile snapshot güncellenmesini, 304 ile snapshot'ın değişmemesini, 500 ile son disk cache'inin kullanılmasını ayrı test edin. CI komutunu xcodebuild test -testPlan FeatureFlags ile sınırlandırmak geri bildirim süresini kısaltır.
App Store yayınlama sırasında feature flag ile kritik hata nasıl kapatılır?
Ağdan gelen değere güvenmek yerine son doğrulanmış kill switch değerini diskten uygulama açılışında okuyun. Yeni davranışı açan flag ile bakım modunu açan flag'i ayırın; bakım modu aktifken ödeme veya veri yazma işlemini istemcide gizlemenin yanında sunucuda da reddedin.
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.



