• 28.08.2026 02:38:24
  • Admin Admin

Bu swiftui eğitimi, SwiftUI diffing maliyetini Instruments ile ölçmeyi; kimlik, Observation ve layout kararlarıyla gereksiz body hesaplarını azaltmayı anlatır. Ölçüm ve CI eşiğiyle regresyonu yakalarsınız.

SwiftUI Eğitiminde Görünüm Kimliği ve Diffing ile Akıcı Ekranlar

SwiftUI eğitimi: Önce body güncellemelerini ölçün

Bir ios eğitimi veya swift eğitimi sırasında ekranın yavaş olduğunu hissetmek yeterli bir teşhis değildir. Aynı fiziksel cihazda, aynı veri setiyle 10 kez liste kaydırma ve 10 kez arama sorgusu değiştirme senaryosu çalıştırın. Xcode Instruments içindeki SwiftUI şablonunda body evaluation sayısını, Time Profiler içinde ana iş parçacığındaki self time değerini ve Animation Hitches verisini kaydedin. Değişiklikten önce ve sonra ortalama yerine p50 ve p95 değerlerini karşılaştırın; tek bir GC, ağ isteği veya termal kısıtlama ortalamayı yanıltabilir.

# Uygulama paket yolunu ve hedef cihaz adını kendi ortamınıza göre değiştirin.
xcrun xctrace record   --template "Time Profiler"   --device "iPhone"   --output FeedBefore.trace   --launch -- /path/to/Build/Products/Release-iphoneos/Catalog.app

İz kaydında sadece uygulamanın toplam CPU yüzdesine bakmayın. Bir arama karakteri girildiğinde çağrı ağacında Array.sorted, String.localizedCaseInsensitiveContains, DateFormatter.string veya JSONDecoder gibi fonksiyonların main thread üzerinde görünmesi somut bir darboğazdır. SwiftUI instrumentında aynı etkileşimde bir üst ekranın body sayısı 1 iken satırların body sayısı 500 ise sorun çoğu zaman diffing değil, satırların gözlemlediği ortak durumdur. Önce bu sayıları not edin, sonra tek bir değişiklik yapıp aynı senaryoyu yeniden kaydedin.

Xcode eğitimi: Kararlı kimlik ile diffing maliyetini sınırlayın

SwiftUI, ForEach elemanlarını kimlik üzerinden eşleştirir. id olarak dizi indeksini kullanmak, filtreleme veya sıralama sonrasında bir ürünün başka bir satırın kimliğini almasına neden olur. Bu durumda SwiftUI satır state'ini yanlış öğeye taşıyabilir, geçiş animasyonlarını bozabilir ve görünüm ağacını gereksiz yeniden kurabilir. Xcode eğitimi kapsamında Debug View Hierarchy ile satır state'ini doğrulayın, ardından Instruments'ta aynı filtreleme etkileşimindeki body evaluation sayısını karşılaştırın.

struct Product: Identifiable, Equatable {
    let id: UUID
    let name: String
    let price: Decimal
    let renderingRevision: Int
}

struct ProductList: View {
    let products: [Product]

    var body: some View {
        List(products) { product in
            ProductRow(product: product)
                .equatable()
        }
    }
}

struct ProductRow: View, Equatable {
    let product: Product

    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.product.id == rhs.product.id &&
        lhs.product.renderingRevision == rhs.product.renderingRevision
    }

    var body: some View {
        HStack {
            Text(product.name)
            Spacer()
            Text(product.price, format: .currency(code: "TRY"))
        }
    }
}

Buradaki renderingRevision alanı sadece satırın ekranda gösterdiği alanlardan biri değiştiğinde artırılmalıdır. Sık yapılan hata, Equatable karşılaştırmasında fiyatı veya indirim rozetini dışarıda bırakmaktır; body yeniden hesaplanmaz ve kullanıcı eski fiyatı görür. Ters hata da tüm Product nesnesini, örneğin analitik sayaçlar veya son senkronizasyon tarihiyle birlikte karşılaştırmaktır. O alanlar değiştikçe görünür çıktı aynı olsa bile satır güncellenir. Ayrıca .id(UUID()) kullanmayın: Her body geçişinde yeni kimlik üretir, @State değerlerini sıfırlar ve .task ile başlatılan işleri iptal edip yeniden başlatır.

iOS MVVM mimarisi içinde Observation bağımlılıklarını bölmek

ios mvvm mimarisi uygulamalarında tek bir @Observable view model içine sorgu, yükleme durumu, seçili öğe ve binlerce satırlık veri koymak görünüşte pratiktir. Ancak View body içinde model.products veya model.filteredProducts okunursa Observation bu özelliklere bağımlılık kaydeder. Bir dizi ataması, onu okuyan her görünümü geçersiz kılar. Çözüm, ekranın değişken kontrol durumunu satırın render verisinden ayırmak ve satıra yalnızca değer tipi bir projection vermektir.

import Observation
import SwiftUI

@Observable
@MainActor
final class CatalogScreenModel {
    var query = ""
    var isRefreshing = false
    private(set) var rows: [ProductRowModel] = []

    func apply(snapshot: [Product]) {
        let normalized = query.folding(options: [.diacriticInsensitive], locale: .current)
        rows = snapshot
            .filter { $0.name.localizedCaseInsensitiveContains(normalized) }
            .map(ProductRowModel.init)
    }
}

struct ProductRowModel: Identifiable, Equatable {
    let id: UUID
    let title: String
    let formattedPrice: String
    let renderingRevision: Int

    init(_ product: Product) {
        id = product.id
        title = product.name
        formattedPrice = product.price.formatted(.currency(code: "TRY"))
        renderingRevision = product.renderingRevision
    }
}

Bu yaklaşımda Decimal biçimleme satırın her body çağrısında değil, snapshot uygulandığında yapılır. Bununla birlikte apply fonksiyonunu her tuş vuruşunda 20 bin kayıt için ana aktörde çalıştırmak yeni bir darboğaz yaratır. Arama verisi büyükse filtreleme ve projection üretimini actor dışında hazırlayın, sonra tek bir rows atamasıyla ana aktöre dönün. Swift 6 strict concurrency denetiminde Product ve ProductRowModel için Sendable gereksinimini açıkça değerlendirin; sınıf referansı taşıyan, thread-safe olmayan formatter veya database nesnesini detached göreve geçirmeniz data race oluşturur.

iPhone uygulama geliştirme için layout geri besleme döngülerini kaldırın

iphone uygulama geliştirme sırasında karmaşık kartlarda GeometryReader ile genişliği ölçüp sonucu @State'e yazmak yaygın bir maliyettir. Layout geçişi state yazımını tetikler, state yazımı body ve layout'u yeniden tetikler. Genişliğe göre sabit sütun sayısı seçmek gibi durumlarda GeometryReader yerine ViewThatFits veya containerRelativeFrame kullanın. Bu API'ler ölçümü SwiftUI layout sistemi içinde tutar ve PreferenceKey üzerinden ek state turu oluşturmaz.

struct ProductCard: View {
    let title: String

    var body: some View {
        ViewThatFits(in: .horizontal) {
            HStack(spacing: 8) {
                Image(systemName: "cart")
                Text(title).lineLimit(1)
            }

            VStack(alignment: .leading, spacing: 4) {
                Image(systemName: "cart")
                Text(title).lineLimit(2)
            }
        }
        .containerRelativeFrame(.horizontal, count: 2, spacing: 12)
    }
}

Bu kodu profile ederken kart içindeki Text'in satır sayısını Dynamic Type'ın büyük bir kategorisinde de test edin. Sadece standart yazı boyutunda tek satıra sığan tasarım, büyük içerik boyutunda HStack seçeneğinin sıkışmasına ve yeniden ölçümlere yol açabilir. Instruments'ta layout kaynaklı hitch görürseniz önce gölgeli, blur'lu ve maskeli katmanları Core Animation instrumentıyla ayırın. Özellikle her satıra .shadow(radius: 12) ve .clipShape uygulanması, kaydırma sırasında offscreen render geçişleri oluşturabilir; gölgeyi tek bir kapsayıcıya taşımak ile aynı kayıt üzerinde hitch sayısını ölçün.

Swift kursu projelerinde performans regresyonunu CI'da yakalamak

Bir swift kursu projesinde ölçümün yerel makinede kalmaması için saf veri dönüşümlerine XCTest performans testi ekleyin. Bu test, SwiftUI frame hızını ölçmez; filtreleme, sıralama ve view state projection maliyetini izler. Görsel kaydırma ve layout maliyeti için Instruments kaydı ayrı kalmalıdır. Test girdisini seed'li fixture ile sabitlemezseniz veri dağılımı değişir ve XCTest baseline karşılaştırması anlamını kaybeder.

final class CatalogProjectionPerformanceTests: XCTestCase {
    func testProjectionForTenThousandProducts() throws {
        let products = ProductFixture.make(count: 10_000, seed: 42)

        measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) {
            let rows = products
                .filter { $0.name.localizedCaseInsensitiveContains("kahve") }
                .sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
                .map(ProductRowModel.init)
            XCTAssertFalse(rows.isEmpty)
        }
    }
}

app store yayınlama öncesinde archive ile kullanılan optimizasyon ayarlarını test ortamında da çalıştırın; Debug derlemesindeki assertion, sanitization ve farklı optimizasyon seviyesi Time Profiler sonuçlarını çarpıtabilir. CI'da ölçümü tek başına kesin bir red kuralı yapmak yerine baseline'a göre anlamlı sapmayı raporlayın ve fiziksel cihazdaki Instruments izini pull request'e ekleyin. Örnek komut:

xcodebuild test   -scheme Catalog   -configuration Release   -destination 'platform=iOS Simulator,name=iPhone 16'   -only-testing:CatalogTests/CatalogProjectionPerformanceTests   -resultBundlePath Performance.xcresult
Simulator cihaz adını CI imajında mevcut bir aygıtla değiştirin. Daha önemlisi, p95 kaydırma hitch sayısı veya body evaluation sayısı gibi UI metriklerini aynı fiziksel test cihazında sürümden sürüme kıyaslayın; farklı Mac işlemcilerinin CPU sürelerini doğrudan eşik olarak kullanmayın.

Sık Sorulan Sorular

SwiftUI eğitimi sırasında List neden her arama karakterinde tüm satırları yeniler?

Önce Instruments SwiftUI şablonunda satır body evaluation sayısını kontrol edin. Satır View'ı doğrudan ortak @Observable view model okuyor veya ForEach kimliği indeksle kuruluyorsa tüm satırlar geçersiz olabilir. Satıra Equatable değer tipi bir ProductRowModel verin, ForEach için kalıcı id kullanın ve görünür alan değişmedikçe renderingRevision değerini artırmayın.

iOS MVVM mimarisi ile SwiftUI performans ölçümü nasıl yapılır?

İki ayrı ölçüm yapın: ViewModel'in filtreleme ve projection fonksiyonlarını XCTClockMetric, XCTCPUMetric ve sabit fixture ile ölçün; gerçek etkileşim için cihazda Instruments SwiftUI, Time Profiler ve Animation Hitches kayıtlarını alın. Önce-sonra karşılaştırmasında aynı sorgu dizisini, aynı kayıt sayısını ve p50/p95 metriklerini kullanın.

Xcode eğitimi için GeometryReader yerine hangi SwiftUI araçları kullanılmalı?

Sadece yatay alana sığan yerleşimi seçmek için ViewThatFits, kapsayıcı genişliğine oranlı kartlar için containerRelativeFrame kullanın. GeometryReader sonucu @State'e yazılıyorsa layout-state-layout döngüsü oluşur. Özel ölçüm gerçekten gerekiyorsa PreferenceKey değerini yalnızca değiştiğinde yazın ve Instruments'ta body sayısının ek layout turuyla artıp artmadığını doğrulayın.

App Store yayınlama öncesi SwiftUI performans testi Debug mı Release mi çalıştırılmalı?

UI profilini dağıtıma yakın Release yapılandırmasında, fiziksel cihazda alın. Debug derlemesindeki ek denetimler ve farklı derleyici optimizasyonu CPU örneklerini değiştirir. Bunun yanında CI'da Release xcodebuild test ile saf dönüşüm performansını izleyin, ancak simülatör sürelerini fiziksel cihaz frame hitch eşiği olarak kullanmayı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