Bu swiftui eğitimi, ProMotion ekranlarda gerçek kare aralığını CADisplayLink ile ölçmeyi, Instruments ile hitch kaynağını ayırmayı ve görüntü çözme işini ana aktörden çıkarmayı ele alır.
SwiftUI Eğitiminde ProMotion'da GPU Frame Pacing Ölçümü Rehberi
SwiftUI eğitimi için frame pacing temel çizgisini kurmak
120 Hz destekleyen bir cihazda hedef kare bütçesi yaklaşık 8.33 ms'dir; ancak uygulamanın her callback'te bu süreyi yakaladığı varsayılmamalıdır. CADisplayLink callback'leri arasındaki gerçek farkı kaydedin. 16.67 ms'den büyük bir aralık, en az bir sunum fırsatının kaçırıldığına işaret eder. Aşağıdaki probu yalnızca debug veya performans deneyi derlemelerinde çalıştırın; her callback'te disk yazmak ölçümün kendisini bozabilir.
import QuartzCore
@MainActor
final class FramePacingProbe: NSObject {
private var link: CADisplayLink?
private var previousTimestamp: CFTimeInterval?
private var samples: [Double] = []
func start() {
let link = CADisplayLink(target: self, selector: #selector(tick(_:)))
link.preferredFrameRateRange = CAFrameRateRange(
minimum: 60,
maximum: 120,
preferred: 120
)
link.add(to: .main, forMode: .common)
self.link = link
}
func stop() {
link?.invalidate()
link = nil
}
@objc private func tick(_ link: CADisplayLink) {
defer { previousTimestamp = link.timestamp }
guard let previousTimestamp else { return }
let intervalMs = (link.timestamp - previousTimestamp) * 1_000
samples.append(intervalMs)
if samples.count == 600 {
let sorted = samples.sorted()
let p95 = sorted[Int(Double(sorted.count - 1) * 0.95)]
let missed = samples.filter { $0 > 12.5 }.count
print("p95: \(p95) ms, missed intervals: \(missed)/600")
samples.removeAll(keepingCapacity: true)
}
}
}Önemli ayrıntı: preferredFrameRateRange bir talep mekanizmasıdır, garanti değildir. Termal durum, Low Power Mode, başka ekran yenileme politikaları ve compositor kararı gerçek ritmi değiştirebilir. Bu nedenle iOS eğitimi veya iphone uygulama geliştirme laboratuvarında sadece FPS sayısı değil, p95 callback aralığı ve 12.5 ms üstündeki örnek sayısı kaydedilmelidir. Aynı akışı en az 30 saniye, cihaz prize takılı değilken ve Release yapılandırmasıyla çalıştırmak daha anlamlı bir karşılaştırma üretir.
Xcode eğitimi kapsamında Instruments ile CPU hitch ve GPU darboğazını ayırmak
Xcode'un Instruments aracında önce Animation Hitches şablonunu açın ve aynı kullanıcı senaryosunu kaydedin: örneğin 500 görsellik bir katalogda 10 saniye boyunca sabit hızla kaydırma. Hitch olayını seçtiğinizde ana iş parçacığındaki stack trace, uzun SwiftUI body hesaplaması veya senkron görüntü çözme gibi CPU kaynaklarını gösterir. Stack trace kısa kalırken hitch sürüyorsa aynı kaydı Metal System Trace ile tekrarlayın; Renderer Utilization, GPU Frame Time ve tile memory artışı GPU veya compositing baskısını ayırır.
Önce-sonra sonucu tek bir ortalama FPS ile raporlamayın. Örneğin başlangıçta Animation Hitches kaydında 10 saniyelik kaydırmada 18 hitch ve 142 ms toplam hitch süresi elde ettiğinizi varsayın. Aşağıdaki değişiklikten sonra aynı cihaz, aynı Release build, aynı veri seti ve aynı kaydırma hızıyla 5 tekrar yapın; medyan hitch sayısını ve p95 CADisplayLink aralığını karşılaştırın. Debug build'deki SwiftUI assertion'ları ve Xcode'a bağlı debugger, bu kıyaslamayı geçersiz kılabilir.
import os
let signposter = OSSignposter(subsystem: "com.acme.catalog", category: "thumbnail")
func decodeThumbnail(_ data: Data, maxPixel: Int) async throws -> CGImage {
let state = signposter.beginInterval("decodeThumbnail")
defer { signposter.endInterval("decodeThumbnail", state) }
return try await Task.detached(priority: .userInitiated) {
try ThumbnailDecoder.downsample(data: data, maxPixel: maxPixel)
}.value
}Bu signpost'lar Time Profiler ve Points of Interest görünümünde thumbnail çözme aralığını hitch zaman çizelgesiyle eşleştirir. Swift kursu içeriğinde sık görülen hata, yalnızca Task { } kullanıp işin yine @MainActor ile izole edilmiş bir metotta çalıştığını fark etmemektir. Task.detached burada CPU ağırlıklı Core Graphics işini aktörden ayırır; UI durum güncellemesi ise tamamlandığında tekrar ana aktörde yapılmalıdır.
iPhone uygulama geliştirme: Thumbnail çözmeyi piksel bütçesine bağlamak
Ağdan 3000 x 4000 piksel JPEG indirip hücrede 96 x 96 point göstermenin maliyeti, dosya boyutundan çok decode edilmiş bitmap boyutudur. 4 byte/piksel kabulüyle bu görsel yaklaşık 45.8 MB ham bellek kaplar. Ekranda 3x ölçekte 96 point için ihtiyaç yaklaşık 288 pikselken, tam çözünürlük decode etmek CPU süresini ve bellek baskısını gereksizce artırır. ImageIO ile indirme anında downsample uygulayın.
import ImageIO
enum ThumbnailDecoder {
static func downsample(data: Data, maxPixel: Int) throws -> CGImage {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
throw CocoaError(.fileReadCorruptFile)
}
let options: CFDictionary = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceThumbnailMaxPixelSize: maxPixel
] as CFDictionary
guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options) else {
throw CocoaError(.fileReadCorruptFile)
}
return image
}
}kCGImageSourceCreateThumbnailWithTransform EXIF orientation bilgisini decode aşamasında uygular. Bu bayrak olmadan bazı kamera fotoğrafları yalnızca belirli cihaz yönlerinde yanlış döner ve hata çoğu test görselinde görünmez. Hedef pikseli ceil(pointSize * UIScreen.main.scale) ile hesaplayın. Aynı URL'nin 96 point liste ve 360 point detay sürümü için tek cache anahtarı kullanmak da bulanık detay ekranı üretir; anahtara hedef piksel boyutunu dahil edin.
SwiftUI'da compositing tetikleyicilerini GPU ölçümüyle doğrulamak
SwiftUI modifier zincirinde .opacity(0.99), .blur, .mask, .drawingGroup() ve geniş alanlı gölgeler ara render hedefi oluşturabilir. Bunun maliyeti cihaz ve sahneye göre değiştiği için kod incelemesinde sadece modifier adına bakarak hüküm vermeyin. Xcode Debug View Hierarchy ile ilgili view katmanlarını inceleyin, ardından Instruments Metal System Trace'te aynı geçiş sırasında GPU Frame Time ve renderer utilization değerlerini kaydedin.
// Kaçının: opacity 1'den küçük olduğu için tüm alt ağacı compositing group'a iter.
VStack(spacing: 12) {
ArtworkView(image: image)
MetadataView(item: item)
}
.opacity(isDimmed ? 0.55 : 0.99)
// Daha dar etki alanı: sadece gerçekten solacak katmanı değiştirin.
VStack(spacing: 12) {
ArtworkView(image: image)
.opacity(isDimmed ? 0.55 : 1.0)
MetadataView(item: item)
}Bu değişiklikte amaç modifier sayısını azaltmak değildir. İlk sürümde metadata dahil bütün alt ağacın ara yüzeye çizilip sonra alpha ile birleştirilmesi mümkünken, ikinci sürümde compositing alanı görselle sınırlanır. Önce Metal System Trace'te 10 saniyelik animasyonda GPU Frame Time medyanını ölçün, ardından değişikliği uygulayıp aynı kaydı alın. GPU zamanı düşmüyor ama Time Profiler'da ana thread uzunsa, sorun compositing değil view state veya decode işidir; yanlış katmanı optimize etmeyin.
iOS MVVM mimarisi ve app store yayınlama öncesi performans kapısı
ios mvvm mimarisi kullanılıyorsa frame pacing'i view'a yayılmış rastgele task'larla değil, ekran akışını temsil eden ViewModel sınırında ölçülebilir olaylarla yönetin. Örneğin arama metninin her karakterinde ağ isteği başlatmak yerine 250 ms debounce, boşluk kırpma ve tekrar eden sorgu elemesi uygulayın. Bu, ana thread'i doğrudan hızlandırdığı için değil, her sonuç dalgasının oluşturduğu diff, thumbnail isteği ve layout hesaplaması sayısını azalttığı için hitch olasılığını düşürür.
import Combine
final class SearchViewModel: ObservableObject {
@Published var query = ""
@Published private(set) var items: [CatalogItem] = []
private var cancellables = Set<AnyCancellable>()
init(api: CatalogAPI) {
$query
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.removeDuplicates()
.debounce(for: .milliseconds(250), scheduler: DispatchQueue.main)
.map { query in
api.searchPublisher(query: query)
.replaceError(with: [])
}
.switchToLatest()
.receive(on: DispatchQueue.main)
.assign(to: &$items)
}
}app store yayınlama öncesinde TestFlight dağıtımından ayrı bir performans kapısı tanımlayın: Release arşivi, gerçek cihaz, sabit test hesabı, sabit veri seti ve 5 tekrarlı Instruments kaydı. Kabul eşiği örneği: katalog kaydırmasında p95 CADisplayLink aralığı 16.67 ms'yi aşmayacak, Animation Hitches toplamı 10 saniyede 3'ü geçmeyecek ve bellek uyarısı alınmayacak. Bu eşikleri CI'da tamamen otomatikleştirmek zor olabilir, fakat sürüm notuna eklenen manuel ölçüm tablosu regresyonun hangi PR'da başladığını izlenebilir hale getirir. Bu yaklaşım swift eğitimi, xcode eğitimi ve ios kursu programlarında gerçek cihaz doğrulamasını emülatör ekran görüntüsü kontrolünden ayırmak için de kullanılabilir.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
SwiftUI eğitimi sırasında ProMotion'da uygulamam neden her zaman 120 FPS göstermiyor?
CADisplayLink için 120 Hz tercih etmek compositor'a bir istek gönderir, sabit yenileme garantisi vermez. Gerçek davranışı 600 callback boyunca link.timestamp farklarını kaydederek ölçün. p95 aralık 16.67 ms civarındaysa önce Animation Hitches ile ana thread stack trace'ini, ardından Metal System Trace ile GPU Frame Time'ı inceleyin.
iPhone uygulama geliştirme projesinde SwiftUI scroll hitch nasıl bulunur?
Release build'i gerçek cihazda Instruments Animation Hitches ile kaydedin ve 10 saniyelik tekrarlanabilir bir kaydırma senaryosu kullanın. Hitch anında Time Profiler stack trace'inde CGImageSource decode, JSON parse veya SwiftUI body hesaplaması görünüyorsa CPU tarafını düzeltin. Stack trace temiz fakat GPU Frame Time yüksekse Metal System Trace ve Debug View Hierarchy ile blur, mask, opacity ve drawingGroup kaynaklı compositing alanlarını kontrol edin.
Swift kursu için AsyncImage yerine downsample edilmiş görsel nasıl kullanılır?
URLSession'dan gelen Data'yı ImageIO ile kCGImageSourceThumbnailMaxPixelSize vererek hedef ekran pikseline downsample edin. Decode işlemini detached task veya decode actor'unda yürütün, sonucu ana aktörde @Published veya SwiftUI state'e atayın. Cache anahtarında URL yanında hedef piksel boyutunu da tutun; aksi halde liste için üretilen küçük bitmap detay ekranında yeniden kullanılabilir.
App Store yayınlama öncesi iOS MVVM mimarisi için hangi performans metrikleri saklanmalı?
Her kritik akış için cihaz modeli, Release build numarası, veri seti sürümü, p95 CADisplayLink aralığı, 10 saniyedeki hitch sayısı, toplam hitch süresi ve Metal System Trace GPU Frame Time medyanını saklayın. ViewModel'de debounce ve switchToLatest gibi değişikliklerden önce ve sonra aynı senaryoyu 5 kez çalıştırıp medyan sonucu karşılaştırı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.


