• 25.08.2026 04:48:54
  • Admin Admin

SwiftUI eğitimi kapsamında soğuk başlangıcı XCTest, Instruments Signpost ve MetricKit ile ölçün; kritik bağımlılıkları ayırıp iOS kursu projelerinde p95 açılış süresini doğrulanabilir biçimde düşürün.

SwiftUI Eğitimi: Başlangıç Süresini Signpost ve MetricKit ile Ölçmek

iOS eğitimi için önce soğuk başlangıç taban çizgisini kurun

Başlangıç optimizasyonunda ilk ayrım soğuk başlangıç, sıcak başlangıç ve foreground dönüşüdür. Soğuk başlangıçta process oluşturma, dynamic linker, uygulama kodu, ilk SwiftUI görünüm ağacı ve ilk frame birlikte maliyet üretir. Xcode test planına gerçek cihazda çalışan bir UI test ekleyin; Simulator sonucu yalnızca regresyon sinyali olarak kullanın. XCTest'in XCTApplicationLaunchMetric metriği, uygulamanın launch süresini tekrarlar boyunca toplar.

import XCTest

final class LaunchMetricsTests: XCTestCase {
    func testColdLaunch() {
        measure(metrics: [XCTApplicationLaunchMetric()]) {
            let app = XCUIApplication()
            app.launchEnvironment["UITEST_RESET_STATE"] = "1"
            app.launch()
            XCTAssertTrue(app.staticTexts["home-ready"].exists)
        }
    }
}

Her ölçümden önce UITEST_RESET_STATE ile UserDefaults, uygulama cache'i ve oturum verisini test kodunda temizleyin. Aksi halde ikinci iterasyonda disk cache veya oturum geri yükleme devreye girer ve soğuk başlangıç karşılaştırması bozulur. En az 20 tekrar kaydedin; ortalama yerine p50 ve p95 değerlerini CI artefact'ına yazın. Örneğin değişiklik öncesi p50 820 ms ve p95 1.240 ms iken, yalnızca ilk frame için zorunlu bağımlılıklar bırakıldıktan sonra aynı cihaz, aynı build configuration ve aynı test verisiyle p50 510 ms, p95 730 ms görülebilir. Bu sayılar hedef değil, karşılaştırmanın nasıl yapılacağını gösteren kayıt biçimidir.

Xcode eğitimi pratiği: Instruments Signpost ile pahalı sınırı bulun

Xcode'da Product > Profile ile Instruments - Points of Interest şablonunu açın. 'Launch' isimli tek bir süreye bakmak yerine container kurulumu, yerel veritabanı açılışı, kimlik yenileme ve ilk ekran modelinin hazırlanması için ayrı interval üretin. Böylece örneğin 600 ms'lik gecikmenin SwiftUI body değerlendirmesinden değil, ana thread'de yapılan SQLite migration kontrolünden kaynaklandığını görebilirsiniz.

import os

@MainActor
final class BootstrapController {
    private let signposter = OSSignposter(
        subsystem: "com.acme.store",
        category: "launch"
    )

    func prepareCriticalState() async {
        let state = signposter.beginInterval("critical-bootstrap")
        defer { signposter.endInterval("critical-bootstrap", state) }

        await SessionStore.shared.restoreCachedSession()
        FeatureCache.shared.loadMemorySnapshot()
    }
}

Signpost interval'ını await öncesinde başlatıp defer ile kapatmak, error veya cancellation yolunda da eşleşen bir bitiş olmasını sağlar. Yaygın hata, aynı isimli interval'ları farklı çağrılarda açık bırakmaktır; Instruments bu durumda iç içe geçmiş ve yorumlanamaz zaman çizelgeleri gösterir. Time Profiler'da signpost çevresindeki main thread stack'ini açın: JSON decode, Keychain erişimi veya büyük bir Core Data fetch görünüyorsa, gecikmeyi 'SwiftUI yavaş' diye etiketlemek yerine o çağrıyı kritik patikadan çıkarın.

iOS MVVM mimarisi ile kritik ve ertelenebilir bağımlılıkları ayırın

iOS MVVM mimarisi içinde launch sırasında bütün repository'leri, analytics SDK'larını ve uzak yapılandırmayı ViewModel init'inde oluşturmak sık rastlanan bir hatadır. Init senkron çalışır; burada yapılan disk I/O veya şifre çözme ilk frame'i geciktirir. Swift eğitimi ve swift kursu örneklerinde ViewModel'i yalnızca ekranda çizim için gerekli snapshot ile başlatın, ağdan gelen veya detay ekranına ait veriyi görünüm görünür olduktan sonra yükleyin.

import Observation
import SwiftUI

@MainActor
@Observable
final class HomeViewModel {
    private let sessionRepository: SessionRepository
    private let catalogRepository: CatalogRepository
    private(set) var greeting = ""
    private(set) var products: [Product] = []

    init(sessionRepository: SessionRepository,
         catalogRepository: CatalogRepository) {
        self.sessionRepository = sessionRepository
        self.catalogRepository = catalogRepository
    }

    func loadCriticalContent() async {
        greeting = await sessionRepository.cachedDisplayName()
    }

    func loadDeferredContent() async {
        products = (try? await catalogRepository.featured()) ?? []
    }
}

struct HomeView: View {
    @State private var model: HomeViewModel

    init(container: AppContainer) {
        _model = State(initialValue: HomeViewModel(
            sessionRepository: container.sessionRepository,
            catalogRepository: container.catalogRepository
        ))
    }

    var body: some View {
        Text(model.greeting).accessibilityIdentifier("home-ready")
            .task { await model.loadCriticalContent() }
            .task(priority: .utility) { await model.loadDeferredContent() }
    }
}

Buradaki iki task'ın mekanizması önemlidir: kritik task tamamlanana kadar yalnızca küçük bir cachedDisplayName okuması yapılır; katalog isteği ise view hiyerarşisi kurulduktan sonra utility önceliğinde planlanır. Ancak iki ayrı task yazmak veri yarışını otomatik olarak engellemez. Aynı @Observable alanını iki task değiştiriyorsa ViewModel'i @MainActor tutun veya mutable durumu ayrı bir actor'a taşıyın. Ayrıca @StateObject veya @State içinde oluşturulan nesnenin init'ine ağır iş koymak da sorunu gizler; property wrapper nesne kimliğini korur, init içindeki bloklayan I/O'yu ertelemez.

iPhone uygulama geliştirme akışında ilk frame'den sonra işi planlayın

İlk ekranın ihtiyacı olmayan işlerde amaç paralellik sayısını artırmak değil, main thread üzerindeki zorunlu işi azaltmaktır. Örneğin launch'ta tüm görsel cache'i taramak yerine yalnızca URLCache metadata'sını hazır bırakın; thumbnail decode işlemini hücre görünür olduğunda yapın. Instruments Time Profiler'da 'Call Tree > Hide System Libraries' ve 'Main Thread' filtresiyle önce-sonra stack'lerini karşılaştırın. Decode maliyeti ImageIO altında görünüyorsa, decode edilmiş UIImage'ı ana thread'de üretmek yerine ImageRenderer veya kendi image pipeline actor'unuzda sınırlandırılmış concurrency kullanın.

actor StartupWorkQueue {
    func warmNonCriticalCaches() async {
        await withTaskGroup(of: Void.self) { group in
            group.addTask { await SearchIndex.shared.openIfNeeded() }
            group.addTask { await RecommendationCache.shared.pruneExpired() }
            group.addTask { await Analytics.shared.rotateSessionIfNeeded() }
        }
    }
}

struct RootView: View {
    let startupQueue = StartupWorkQueue()

    var body: some View {
        HomeView(container: .production)
            .task(priority: .background) {
                await startupQueue.warmNonCriticalCaches()
            }
    }
}

Bu örnekte actor, üç cache görevinin aynı mutable dosya indeksini eşzamanlı bozmasını engeller; fakat actor içinde CPU-ağır senkron bir döngü yazarsanız actor executor'ını yine bloke edersiniz. Her görev için cancellation kontrolü ekleyin ve kullanıcı ekranı kapattığında .task'ın iptal edilmesini test edin. Network Link Conditioner ile yüksek gecikme profili açıp uygulamanın ilk ekranının ağ beklemeden çizildiğini doğrulayın. Ağ yanıtını launch başarısı için zorunlu hale getirmek, p95'i doğrudan kullanıcının bağlantı kalitesine bağlar.

App Store yayınlama öncesi MetricKit ile sahadaki launch verisini okuyun

CI'daki XCTest ölçümü kontrollü ortam verir, ancak app store yayınlama sonrasında farklı cihaz belleği, storage basıncı ve gerçek kullanıcı verisi etkisini görmek için MetricKit ekleyin. MXAppLaunchMetric toplu rapor sağlar; callback'in anlık telemetry olmadığını, sistemin raporları daha sonra teslim ettiğini kabul ederek tasarlayın. Bu nedenle release anında alarm üretmek yerine sürüm bazlı p95 değişimini analiz aracınıza gönderin.

import MetricKit
import os

final class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
    static let shared = MetricsSubscriber()

    func start() {
        MXMetricManager.shared.add(self)
    }

    func receive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            guard let data = try? JSONEncoder().encode(payload),
                  let json = String(data: data, encoding: .utf8) else { continue }
            Logger(subsystem: "com.acme.store", category: "metrickit")
                .info("metric payload: \(json, privacy: .private(mask: .hash))")
        }
    }
}

@main
struct StoreApp: App {
    init() {
        MetricsSubscriber.shared.start()
    }

    var body: some Scene {
        WindowGroup { RootView() }
    }
}

Subscriber'ı static instance ile yaşamda tutun; geçici bir nesneye add çağrısı yapmak callback'in kaybolmasına yol açabilir. Payload'ı doğrudan loglamak yerine üretim uygulamasında launch histogramını, build numarasını ve cihaz sınıfını ayrıştırıp PII içermeyen bir şemaya dönüştürün. Sonra aynı release için MetricKit histogramı ile XCTest p95'ini yan yana koyun: laboratuvar sonucu sabit kalıp saha metriği kötüleşiyorsa, test datasında bulunmayan migration, büyük kullanıcı cache'i veya düşük depolama alanı gibi koşulları yeniden üretmeniz gerekir.

Sık Sorulan Sorular

SwiftUI eğitimi sırasında uygulama başlangıç süresini hangi araçla ölçmeliyim?

Tek bir araç yerine üç katman kullanın: CI'da XCTApplicationLaunchMetric ile 20+ tekrar ve p95, yerel neden analizi için Instruments Points of Interest ile OSSignposter interval'ları, yayın sonrası dağılım için MetricKit MXAppLaunchMetric. Ölçümleri aynı cihaz, aynı build configuration ve temizlenmiş test verisiyle karşılaştırın.

iOS MVVM mimarisi launch süresini neden artırır?

MVVM tek başına süre eklemez; ViewModel init zincirinde repository oluşturma, migration kontrolü, Keychain okuma veya senkron decode varsa bu işler ilk görünüm çizilmeden çalışır. ViewModel init'ini bağımlılık atamasıyla sınırlayın, cached UI state'i loadCriticalContent içinde okuyun ve katalog, indeks, analytics gibi işleri RootView .task içinde ertelenmiş olarak başlatın.

Swift kursu projesinde MetricKit verisi neden hemen gelmiyor?

MetricKit callback'i gerçek zamanlı event hattı değildir; işletim sistemi metrikleri toplu olarak teslim eder. Hızlı doğrulama için Instruments ve XCTest kullanın. MetricKit tarafında ise payload'ı sürüm, cihaz sınıfı ve launch histogramıyla saklayıp release sonrası p95 regresyonlarını izleyin.

Xcode eğitimi için Signpost interval'ını async fonksiyonda nereye koymalıyım?

Interval'ı ölçmek istediğiniz async işten hemen önce beginInterval ile açın ve defer içinde endInterval çağırın. Böylece throw veya cancellation durumunda interval kapanır. Aynı isimli eşzamanlı interval'ları ayırt etmek için category'leri bootstrap, database ve auth gibi sabit sınırlara bölün.

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.

Opendart Akademi llms.txt