SwiftUI eğitiminde liste akıcılığını ölçülebilir hale getirin: Animation Hitches ile darboğazı bulun, kararlı kimlik kullanın, görselleri hedef piksel boyutunda decode edin ve sürüm öncesi regresyon eşiği koyun.
SwiftUI Eğitiminde Akıcı Listeler: Hitches, Kimlik ve Görsel Borç
SwiftUI eğitimi için önce hitch ölçümü ve tekrar üretilebilir senaryo
Liste akıcılığı sorununu Xcode Preview veya Simulator'da değerlendirmeyin. Metal sürücüsü, GPU zamanlaması ve termal davranış fiziksel cihazdan farklıdır. Xcode Instruments içindeki Animation Hitches şablonuyla Release yapılandırmasında fiziksel cihazı kaydedin. Aynı veri setiyle, aynı ağ koşulunda, listenin başından sonuna üç kez hızlı kaydırma yapın; her koşunun hitch sayısını ve en uzun hitch süresini not edin. Değişiklikten sonra tek bir iyi koşu yerine üç koşunun medyanını karşılaştırın. Bu yaklaşım, ilk çalıştırmadaki image cache ısınmasının sonucu bozmasını azaltır.
Bir ios eğitimi veya ios kursu içinde ölçüm disiplinini öğretmek, yalnızca ScrollView seçimini anlatmaktan daha değerlidir. swift eğitimi, swift kursu, swiftui eğitimi ve xcode eğitimi içeriklerinde aynı senaryo yeniden kullanılabilir: 500 satır, her satırda uzak küçük görsel, fiyat ve rozet. iphone uygulama geliştirme işinde gerçek kataloglar bu desene yakındır; app store yayınlama öncesinde ise cihazdaki kaydırma hitches'ları inceleme ekibinden önce sizin yakalamanız gerekir.
Kaydırma sırasında hangi uygulama işinin zaman aldığını ayırmak için veri yenilemelerine signpost ekleyin. Animation Hitches kaydında hitch ile bu işaretlerin çakışması, sorunun çizim mi yoksa yeni veri işleme mi olduğunu gösterir. Örneğin aşağıdaki işaret 120 ms süren JSON decode ve sıralamanın hızlı kaydırma anında tetiklendiğini gösterebilir.
import os.signpost
private let feedLog = OSLog(
subsystem: Bundle.main.bundleIdentifier ?? "com.example.catalog",
category: "FeedRefresh"
)
func refresh() async throws {
os_signpost(.begin, log: feedLog, name: "Refresh")
defer { os_signpost(.end, log: feedLog, name: "Refresh") }
let (data, _) = try await URLSession.shared.data(from: endpoint)
let response = try JSONDecoder().decode(CatalogResponse.self, from: data)
products = response.products.sorted { $0.rank < $1.rank }
}Time Profiler'da bu signpost aralığına inip `JSONDecoder`, `sorted(by:)` veya image decode çağrı ağacını açın. Hitch tam bu aralığa denk gelmiyorsa, refresh kodunu rastgele optimize etmek yerine SwiftUI ve Core Animation izlerini inceleyin.iOS MVVM mimarisi ile kararlı kimlik ve dar veri akışı
SwiftUI'nin liste diff'i satır kimliğine dayanır. ViewModel her yenilemede `UUID()` üretirse veya API DTO'sunu yeni bir `id` ile map ederse, framework eski satırları silinmiş ve yenileri eklenmiş kabul eder. Bunun sonucu yalnızca yeniden çizim değildir: satırların `@State` değeri sıfırlanabilir, görünür görseller yeniden istenebilir ve diff maliyeti artar. ios mvvm mimarisi içinde kalıcı sunucu anahtarını `Identifiable.id` yapın; sıralama, filtreleme ve para biçimlendirme gibi türetilmiş verileri `body` içinde değil ViewModel'de veya önceden hesaplanmış modelde üretin.
struct Product: Identifiable, Equatable, Sendable {
let id: String // Sunucunun kalıcı productId değeri
let title: String
let priceText: String // ViewModel'de bir kez hazırlanır
let thumbnailURL: URL?
}
@MainActor
final class CatalogViewModel: ObservableObject {
@Published private(set) var visibleProducts: [Product] = []
func apply(_ response: CatalogResponse, query: String) {
visibleProducts = response.products
.filter { query.isEmpty || $0.name.localizedCaseInsensitiveContains(query) }
.sorted { $0.rank < $1.rank }
.map {
Product(
id: $0.productId,
title: $0.name,
priceText: $0.price.formatted(.currency(code: $0.currencyCode)),
thumbnailURL: $0.thumbnailURL
)
}
}
}
List(viewModel.visibleProducts) { product in
ProductRow(product: product)
}Burada `priceText` hesaplamasının satır `body` çalıştıkça tekrarlanmaması özellikle uzun listede önemlidir; `formatted` çağrısı locale ve format stili çözümlemesi yapar.Satır gerçekten yalnızca model alanlarıyla çiziliyorsa `Equatable` ve `.equatable()` eklemek, üst görünümdeki ilgisiz state değişimlerinin satır body'lerine yayılmasını kesebilir. Ancak bu tekniği Dynamic Type, renk şeması veya environment'tan gelen değerle görünümü değişen satırlara körlemesine uygulamayın. Eşitlik bu girdileri kapsamazsa SwiftUI satırı atlayabilir ve arayüz eski görünür. Instruments'taki SwiftUI instrumentında önce `ProductRow.body` çağrı sayısını kaydedin, sonra bu değişikliği yapıp aynı kaydırma senaryosunda çağrı sayısı ile hitch medyanını birlikte karşılaştırın.
Görsel borcunu hedef pikselde decode ederek azaltın
Bir JPEG dosyasının ağ boyutu küçük olsa bile decode edilmiş bitmap bellekte büyüktür. Örneğin 3000 x 2000 piksel RGBA görsel yaklaşık 22.9 MiB piksel tamponu gerektirir. Satırda 56 pt görüntülenecek bir görseli tam çözünürlükte decode edip sonra küçültmek, CPU ve bellek bant genişliğini kaydırma anına taşır. `AsyncImage` tek başına kaynağı satırın hedef piksel boyutuna indirmez; CDN'den uygun boyut istemek ilk tercihtir, mümkün değilse ImageIO ile downsample edin.
import ImageIO
import UIKit
enum ThumbnailDecoder {
nonisolated static func decode(
data: Data,
pointSize: CGFloat,
displayScale: CGFloat
) -> CGImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
return nil
}
let options: CFDictionary = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: Int(pointSize * displayScale),
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceShouldCacheImmediately: true
] as CFDictionary
return CGImageSourceCreateThumbnailAtIndex(source, 0, options)
}
}
func loadThumbnail(from url: URL, pointSize: CGFloat, scale: CGFloat) async throws -> CGImage? {
let (data, _) = try await URLSession.shared.data(from: url)
try Task.checkCancellation()
return await Task.detached(priority: .utility) {
ThumbnailDecoder.decode(data: data, pointSize: pointSize, displayScale: scale)
}.value
}Satır kaybolduğunda thumbnail görevini iptal edin ve `Task.checkCancellation()` sonrasında sonucu UI'a yazın. Aksi halde görünür olmayan 100 satırın decode işi utility kuyrukta birikerek görünür satırların bellek baskısını yükseltir.ImageIO'daki `kCGImageSourceCreateThumbnailWithTransform` EXIF yön bilgisini uygular. Bu anahtar atlanırsa bazı kamera görselleri yatık görünür; bu hata test görselleri yön metadatası taşımadığında fark edilmez. Önce-sonra karşılaştırmasında Instruments Time Profiler'da `CGImageSourceCreateThumbnailAtIndex` toplam süresini ve Allocations'ta transient `VM: CG raster data` artışını izleyin. Başarılı değişiklik, yalnızca daha az hitch değil, aynı kaydırma rotasında daha düşük tepe raster bellek değeridir.
SwiftUI liste düzeninde ölçüm döngülerini görünür kılın
`GeometryReader`'ı her satıra koyup genişliği preference key ile üst görünüme geri yazmak, kaydırma sırasında iki yönlü bir düzen geri beslemesi oluşturabilir: satır ölçülür, üst state değişir, liste yeniden değerlendirilir, satırlar tekrar ölçülür. Xcode Instruments içindeki SwiftUI instrumentında `View Body` ve `Layout` zaman çizelgelerini açın. Hızlı kaydırmada aynı satır türü için art arda layout pass'leri görüyorsanız, önce bu ölçüm zincirini kaldırın.
Sabit tasarımlı katalog satırında doğrudan kısıt vermek daha ucuz ve daha öngörülebilirdir. Aşağıdaki örnekte thumbnail alanı her satırda aynı olduğundan parent genişliğini satıra taşımaya gerek yoktur.
struct ProductRow: View {
let product: Product
let image: Image?
var body: some View {
HStack(spacing: 12) {
Group {
if let image {
image.resizable().scaledToFill()
} else {
Color.secondary.opacity(0.15)
}
}
.frame(width: 56, height: 56)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading, spacing: 4) {
Text(product.title).lineLimit(2)
Text(product.priceText).font(.footnote)
}
}
.frame(minHeight: 72)
}
}`scaledToFill()` sonrasında `clipShape` kullanmak, kaynak en-boy oranı farklı olduğunda hücre taşmasını engeller. Ancak satırın yüksekliği metinle değişebiliyorsa sabit `frame(height:)` kullanıp erişilebilir büyük yazıları kesmeyin; fiziksel cihazda en büyük Dynamic Type kategorisiyle ayrıca trace alın.Genişlik gerçekten parent'a bağlıysa, satır başına GeometryReader yerine iOS'un ilgili sürümlerindeki `containerRelativeFrame` gibi container tabanlı API'leri veya tek bir üst seviye ölçümü tercih edin. Kararınızı ölçümle doğrulayın: değişiklik öncesi ve sonrası SwiftUI instrumentında `ProductRow` için layout sayısını, Animation Hitches'ta da 16.67 ms ve 33.33 ms üstü hitch kümelerini aynı veri setinde karşılaştırın. Sadece CPU yüzdesine bakmak yanıltıcıdır; kısa fakat ana thread'e kötü zamanda gelen iş de tek kareyi kaçırabilir.
App Store yayınlama öncesi cihaz telemetrisi ve regresyon eşiği
App Store yayınlama sürecini performans testi yerine koymayın. İnceleme sonucunun sorunsuz olması, katalog ekranınızda belirli cihazlarda scroll hitch olmadığı anlamına gelmez. Release adayında fiziksel cihazla Instruments kaydı alın, trace dosyasını build numarasıyla saklayın ve örneğin 500 satırlık senaryoda medyan hitch sayısının kabul edilen taban değerin yüzde 15 üstüne çıkmasını regresyon olarak işaretleyin. Eşik, ekibin kendi ilk güvenilir ölçümünden türetilmelidir; internetteki tek bir fps sayısını evrensel hedef kabul etmeyin.
Sahadan gecikmeli ve toplulaştırılmış sinyal almak için MetricKit payload'larını kaydedin. `MXMetricManager` subscriber'ı uygulama açılışında bir kez eklenmelidir; payload anlık debug aracı değildir ve kullanıcı gizliliğine uygun toplulaştırılmış veri olarak ele alınmalıdır.
import MetricKit
final class MetricsSubscriber: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
let json = payload.jsonRepresentation()
MetricsUploader.enqueue(json, source: "metrickit")
}
}
}
@main
struct CatalogApp: App {
private let metricsSubscriber = MetricsSubscriber()
init() {
MXMetricManager.shared.add(metricsSubscriber)
}
var body: some Scene {
WindowGroup { CatalogScreen() }
}
}Backend tarafında animation metric alanlarını build numarası, cihaz sınıfı ve ekran akışıyla gruplayın. Yeni sürümde hitch oranı yükselirse önce Instruments ile yeniden üretin; MetricKit verisi hangi kullanıcı kümesinde sorun olduğunu söyler, ana thread'deki çağrı ağacını söylemez.İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
SwiftUI eğitiminde List mi LazyVStack mi daha az hitch üretir?
Önceden karar vermeyin. Aynı 500 öğelik veriyle List ve ScrollView + LazyVStack için fiziksel cihazda Animation Hitches trace'i alın. List, platformun liste davranışlarından yararlanabilir; LazyVStack ise görünüm yaşam döngüsü ve state tutma davranışı nedeniyle uzun akışlarda farklı bellek profili gösterebilir. Seçimi hitch medyanı, tepe raster bellek ve satır body çağrı sayısıyla yapın.
iOS MVVM mimarisi SwiftUI liste performansını nasıl etkiler?
ViewModel'in her arama karakterinde tüm DTO listesini yeniden map edip yeni UUID üretmesi SwiftUI diff'ini pahalılaştırır ve satır state'ini sıfırlayabilir. Kalıcı API kimliğini kullanın, filtrelenmiş diziyi ViewModel'de üretin ve fiyat metni gibi türetilmiş alanları modelde saklayın. Sonra SwiftUI instrumentında ProductRow.body çağrı sayısını önce-sonra karşılaştırın.
iPhone uygulama geliştirme sırasında AsyncImage neden kaydırmayı taktırır?
Sorun çoğunlukla ağ isteğinin kendisi değil, büyük görselin decode edilip GPU'ya yüklenmesidir. URL 56 pt thumbnail gösterse de 3000 piksel kaynak indirebilir. CDN'den boyut parametreli thumbnail isteyin veya ImageIO ile `kCGImageSourceThumbnailMaxPixelSize` değerini nokta boyutu x ekran ölçeği olarak ayarlayın; Time Profiler ve Allocations ile raster bellek farkını ölçün.
Xcode eğitimi kapsamında performans testi Simulator'da yeterli mi?
Hayır. Simulator'daki CPU, bellek ve grafik zamanlaması hedef cihazı temsil etmez. Simulator hızlı geri bildirim için kullanılabilir, ancak sürüm kararı için Release derlemesini fiziksel cihazda Animation Hitches, Time Profiler ve Allocations ile kaydedin. Her iki ölçümde de aynı veri setini, aynı kaydırma rotasını ve en az üç tekrar kullanı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.


