SwiftUI eğitimi kapsamında uzak görselli akışlarda bellek, ağ ve kaydırma maliyetini ölçün; actor tabanlı cache, görev iptali ve Instruments ile doğrulanabilir bir görüntü hattı kurun.
SwiftUI Eğitiminde Liste Görselleri: Cache, Prefetch ve İptal
SwiftUI eğitimi: Sorunu Instruments ile önce ölçün
Uzak görseller içeren bir List ekranında ilk iş, “kaydırma takılıyor” gözlemini ölçüye çevirmektir. Xcode eğitiminde kullanılacak somut akış şudur: uygulamayı gerçek cihazda Product > Profile > Time Profiler ile açın, 20-30 saniye boyunca aynı hızda aşağı-yukarı kaydırın ve ana iş parçacığında JPEG/PNG decode, swift_getObjectType veya view body hesaplarının örneklenip örneklenmediğine bakın. Ardından Allocations aracında yaşayan CGImage/UIImage sayısını, Network aracında aynı URL için tekrar eden istekleri kaydedin. Ölçümü karşılaştırılabilir tutmak için cihaz, ağ profili ve veri seti aynı kalmalıdır; örneğin 300 satırlık sabit JSON ve Network Link Conditioner ile sabit bir gecikme kullanın.
Her satırın görünme süresini işaretlemek için OSSignposter ekleyin. Bu, Time Profiler’daki genel CPU örneklerinden farklı olarak bir hücrenin görünmesinden görselin ekrana konmasına kadar geçen süreyi Points of Interest zaman çizelgesinde verir. Önce mevcut uygulamada p50/p95 sürelerini alın; aşağıdaki pipeline değişikliğinden sonra aynı senaryoyu tekrar oynatın. Hedef, “hızlandı” demek değil, örneğin p95 görsel hazır olma süresinin ve ana thread decode örneklerinin hangi oranda değiştiğini raporlamaktır.
import os
let imageSignposter = OSSignposter(subsystem: "com.acme.feed", category: "image")
func loadImage(_ url: URL) async throws -> UIImage {
let state = imageSignposter.beginInterval("ImageLoad", id: .exclusive,
"url=%{public}s", url.absoluteString)
defer { imageSignposter.endInterval("ImageLoad", state) }
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200,
let image = UIImage(data: data) else {
throw URLError(.cannotDecodeContentData)
}
return image
}iOS eğitimi için actor tabanlı görsel pipeline ve maliyetli cache
NSCache<NSURL, UIImage> thread-safe olsa da “aynı URL için aynı anda beş indirme” problemini tek başına çözmez. Hızlı kaydırmada beş satır aynı avatar URL’sini talep ettiğinde, ayrı Task’lar ayrı ağ istekleri ve decode işlemleri başlatabilir. Aşağıdaki actor, tamamlanmış görselleri maliyetle saklar ve devam eden isteği URL bazında birleştirir. totalCostLimit byte garantisi değildir; sistem bellek baskısında cache’i ayrıca boşaltabilir. Bu nedenle maliyeti width × height × 4 olarak vermek, tüm görsellere sabit 1 maliyeti vermekten daha anlamlı bir tahliye önceliği oluşturur.
import UIKit
actor ImagePipeline {
private let cache = NSCache<NSURL, UIImage>()
private var inFlight: [URL: Task<UIImage, Error>] = [:]
init(memoryLimit: Int = 80 * 1024 * 1024) {
cache.totalCostLimit = memoryLimit
}
func image(for url: URL) async throws -> UIImage {
if let cached = cache.object(forKey: url as NSURL) { return cached }
if let task = inFlight[url] { return try await task.value }
let task = Task<UIImage, Error> {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200...299).contains(http.statusCode),
let image = UIImage(data: data) else {
throw URLError(.cannotDecodeContentData)
}
return image
}
inFlight[url] = task
do {
let image = try await task.value
let cost = Int(image.size.width * image.size.height * image.scale * image.scale * 4)
cache.setObject(image, forKey: url as NSURL, cost: cost)
inFlight[url] = nil
return image
} catch {
inFlight[url] = nil
throw error
}
}
}Buradaki önemli edge case, çağıranın iptal edilmesidir: bir satır ekrandan çıktı diye ortak inFlight task’ını doğrudan iptal etmek, aynı URL’yi hâlâ gösteren başka satırın da görselsiz kalmasına yol açar. Önce istek birleştirme uygulanmalı; ağ task’ını iptal etmek isteniyorsa URL başına tüketici sayacı veya referans sayımlı bir abonelik modeli gerekir. Üretimde cache anahtarına yalnızca URL koymak da hatalı olabilir: CDN aynı URL’de Accept, kullanıcı yetkisi veya image transform parametresine göre farklı içerik döndürüyor ise normalize edilmiş URL ve ilgili varyant bilgisini anahtara dahil edin.
iPhone uygulama geliştirme: Hücre kimliğiyle görev iptali ve prefetch
SwiftUI satırında .task {} yerine .task(id: model.imageURL) kullanın. Satır yeniden kullanılırken veya model değişirken SwiftUI eski görevi iptal eder ve URL değişimi yeni yüklemeyi tetikler. Ancak Task.isCancelled kontrolü yalnızca UI’a sonucu yazmadan önce yapılmalıdır; pipeline içindeki ortak indirmeyi körlemesine iptal etmek yukarıdaki deduplikasyonu bozar. Aşağıdaki görünüm, state güncellemesini @MainActor üzerinde tutar; bu, actor’dan dönen UIImage için yarış koşulu yaratmadan placeholder’ın doğru satırda kalmasını sağlar.
struct AvatarRow: View {
let user: User
let pipeline: ImagePipeline
@State private var image: UIImage?
var body: some View {
HStack {
Group {
if let image { Image(uiImage: image).resizable() }
else { Color.gray.opacity(0.2) }
}
.frame(width: 44, height: 44)
.clipShape(Circle())
Text(user.name)
}
.task(id: user.avatarURL) {
image = nil
do {
let loaded = try await pipeline.image(for: user.avatarURL)
guard !Task.isCancelled else { return }
image = loaded
} catch is CancellationError {
// Görünüm artık bu URL'yi göstermiyor.
} catch {
// Telemetriye URL ve HTTP hata bağlamını gönderin.
}
}
}
}Prefetch’i görünür satırın body’sinden başlatmak yerine sayfalama katmanında, görünür indeksin örneğin 12 satır sonrasını tetikleyecek şekilde kurun. onAppear tek başına güvenilir bir görünürlük sinyali değildir: SwiftUI yerleşim hesapları sırasında ekran dışı view’ları da oluşturabilir. scrollPosition(id:) ile görünür kimliği izleyebildiğiniz hedeflerde veya UIKit köprüsünde UICollectionViewDataSourcePrefetching ile gerçek prefetch callback’inde, maksimum eşzamanlılığı 2-4 ile sınırlayın. Aksi halde prefetch, kullanıcıya yakın görsellerin bant genişliğini kapar ve Network Instruments’ta öncelikli isteklere göre daha uzun kuyruklar üretir.
Swift kursu projelerinde decode boyutunu ve HTTP cache davranışını doğrulayın
Sunucudan 4000×3000 fotoğraf indirip ekranda 44×44 göstermek, ağdan sonra decode ve bellek maliyetini büyütür: sıkıştırılmış JPEG küçük olsa bile decode edilmiş bitmap yaklaşık 4000 × 3000 × 4 = 48 MB olabilir. En doğru çözüm CDN’den hedef piksele yakın varyant istemektir; örneğin URL üreticinizde ?w=132&h=132&fit=cover&dpr=3 gibi, servisinizin desteklediği dönüşümü kullanın. İstemci tarafında URLSession’ın HTTP cache’ini devre dışı bırakan reloadIgnoringLocalCacheData politikalarından kaçının; ETag veya Cache-Control yanıtları varken bu politika her liste dönüşünde koşullu isteği bile gereksiz tam isteğe çevirebilir.
Cache’in gerçekten kullanıldığını test etmek için Charles Proxy, Proxyman veya URLSession delegate loglarıyla ikinci açılışta 304 Not Modified ya da disk cache davranışını inceleyin. Test ortamında deterministik kontrol için URLProtocol ile ilk çağrıda 200+ETag, ikinci çağrıda If-None-Match bekleyip 304 döndürün. Bu ayrım önemlidir: uygulama içi NSCache süreç sonlanınca kaybolur, HTTP disk cache ise uygun response header’ları varsa uygulama açılışları arasında çalışabilir.
let config = URLSessionConfiguration.default
config.requestCachePolicy = .useProtocolCachePolicy
config.urlCache = URLCache(memoryCapacity: 20 * 1024 * 1024,
diskCapacity: 100 * 1024 * 1024)
let session = URLSession(configuration: config)
// Pipeline'da URLSession.shared yerine session kullanın.iOS MVVM mimarisi ve app store yayınlama öncesi regresyon eşiği
iOS MVVM mimarisinde view model’in görevi ham Data veya UIImage deposuna dönüşmek değildir; görünür satırlar, sayfalama ve hata durumlarını yayınlar, indirme/cache politikası ayrı bir ImagePipeline bağımlılığında kalır. Böylece view model testinde sahte pipeline enjekte edilerek aynı URL’nin kaç kez istendiği doğrulanabilir. Bu ayrım, @MainActor view model’in içinde pahalı image decode yapılmasını da engeller; decode’un main thread’e taşınması Time Profiler’da kaydırma hitch’ı olarak görünür.
App Store yayınlama öncesinde release yapılandırmasıyla gerçek cihazda bir performans kabul eşiği belirleyin: aynı 300 satırlık feed senaryosunda Instruments trace’ini arşivleyin, peak resident memory, tekrar eden URL istek sayısı ve OSSignposter p95 yükleme süresini önceki build ile karşılaştırın. XCTest tarafında en azından feed açılışındaki regresyonu XCTMetric ile CI’da yakalayabilirsiniz. Bu yaklaşım ios kursu, swift eğitimi veya swift kursu projelerinde “simülatörde sorunsuz” sonucundan daha değerlidir; simülatörün bellek baskısı, GPU ve ağ davranışı cihazla aynı değildir.
func testFeedLaunchPerformance() throws {
let app = XCUIApplication()
measure(metrics: [XCTApplicationLaunchMetric(), XCTMemoryMetric()]) {
app.launchArguments = ["-UITestFeedFixture", "300"]
app.launch()
XCTAssertTrue(app.staticTexts["User 0"].waitForExistence(timeout: 5))
app.terminate()
}
}İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
SwiftUI eğitiminde AsyncImage yerine özel image pipeline ne zaman gerekir?
AsyncImage prototip veya düşük trafikli ekran için yeterli olabilir; fakat URL bazlı istek birleştirme, byte maliyetli bellek sınırı, telemetri ve öncelikli prefetch gerektiğinde actor tabanlı bir pipeline kullanın. Kararı Time Profiler, Allocations ve Network Instruments ile aynı URL’nin tekrar indirilmesi veya ana thread decode örnekleri üzerinden verin.
iOS MVVM mimarisinde UIImage cache view model içinde mi olmalı?
Hayır; view model görünüm durumunu ve sayfalama kararını taşımalı, cache ve URLSession politikası enjekte edilen bir ImagePipeline servisinde kalmalıdır. Bu sınır sayesinde unit testte pipeline yerine sayaç tutan fake verip aynı avatar URL’si için tek mantıksal istek üretildiğini doğrulayabilirsiniz.
iPhone uygulama geliştirme sırasında SwiftUI List kaydırma takılması nasıl ölçülür?
Release build’i fiziksel cihazda Product > Profile ile açın; Time Profiler’da main thread örneklerini, Allocations’ta CGImage/UIImage yaşamını ve Network Instruments’ta tekrar istekleri inceleyin. Görsel yükleme süresini OSSignposter ile işaretleyip değişiklik öncesi ve sonrası aynı fixture, cihaz ve ağ koşulunda p50/p95 değerlerini karşılaştırın.
app store yayınlama öncesi görsel bellek regresyonu nasıl yakalanır?
Sabit feed fixture’ı ile XCTest’te XCTMemoryMetric kullanın, ayrıca gerçek cihazda Allocations trace’ini saklayın. CI eşiğini tek bir mutlak MB değeri yerine önceki onaylı build’e göre artış yüzdesi ve 300 satırlık senaryoda yaşayan bitmap sayısı ile tanımlayın; farklı cihazların temel bellek tüketimi farklıdır.
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.


