App’te scroll lag, animation stutter, frame drop. Users “app yavaş” diyor ama root cause bulmak zor. CPU profile yüksek görünmüyor. Memory pressure yok. Problem rendering katmanında.
iOS rendering Core Animation üzerine kurulu. Her view bir CALayer, her property animatable. Ama yanlış kullanıldığında rendering pipeline boğuluyor. Xcode Instruments Core Animation tool’u bu sorunu izolasyon ediyor.
Bu yazıda debugging workflow’umu paylaşacağım.
Rendering pipeline basics
iOS 60fps hedefliyor (iPhone 15 Pro+ 120fps). Her frame 16.67ms (60fps) veya 8.33ms (120fps). Bu süre içinde CPU + GPU rendering tamamlamalı.
Frame zamanında tamamlanmazsa: frame drop. Kullanıcı jank hissediyor. Trust seviyesinde: tek tük drop hissedilmiyor, sürekli drop “app yavaş” kategorisinde.
Core Animation pipeline:
- Commit phase: UIKit layer tree’yi CA’ya teslim eder
- Render server: Layer tree’yi GPU’ya gönderir
- GPU compositing: Layer’lar birleştirilir
- Display: Screen’e paint
Bottleneck bu 4 aşamadan herhangi birinde olabilir.
Instruments Core Animation tool
Xcode’da Product > Profile > Core Animation template.
Tool açılınca:
- FPS meter: Actual frame rate
- Dropped frames counter
- Offscreen render counter
- Blended layer counter
App’i çalıştır, suspected slow flow’u execute et. Metric’leri izle.
FPS 60’a yakın = OK. 40’a düşüyor = problem. 20 altı = ciddi.
Offscreen rendering
En yaygın performance killer. GPU bir layer’ı render ederken offscreen buffer’a önce yazıyorsa, sonra main buffer’a kopyalıyorsa: “offscreen pass”. Expensive.
Offscreen rendering nedenleri:
1. corner radius + masksToBounds/masksToCornerCurve:
view.layer.cornerRadius = 8
view.layer.masksToBounds = true // Offscreen render!Fix: iOS 13+ için performance improved. Ama heavy corner radius yine pahalı.
Alternatif: cornerCurve continuous + smaller radius.
2. shadowPath olmayan shadow:
view.layer.shadowRadius = 10
view.layer.shadowOpacity = 0.3
// shadowPath not set → offscreen renderFix: Explicit shadowPath ver:
view.layer.shadowPath = UIBezierPath(rect: view.bounds).cgPathShadowPath set edildiğinde GPU shadow’u çok daha hızlı hesaplıyor.
3. rasterize:
view.layer.shouldRasterize = trueİyi kullanılırsa faydalı. Yanlış kullanılırsa continuous re-rasterization, çok pahalı.
4. masksToBounds + non-opaque content:
Bu kombinasyon rendering pipeline’ını karmaşıklaştırıyor.
Blended layers
Opacity < 1.0 olan veya transparent background'lu layer'lar "blended" olarak render ediliyor. GPU bir layer'ın pixel'lerini alttaki layer'larla blend ediyor. CPU-intensive.
Instruments’da blended layers kırmızı/yeşil highlighting ile görünüyor.
Fix’ler:
1. Opaque yap:
view.backgroundColor = .white // solid color
view.isOpaque = true2. Parent’ın background’unu child’a taşı:
// Before
parent.backgroundColor = .clear
child.backgroundColor = .white
// After
parent.backgroundColor = .white
child.backgroundColor = .clear // transparent OK now3. Shadow için path kullan (yine): Shadow opacity transparency yaratıyor.
Color Misaligned Images
Image sınırları pixel grid’e align değilse GPU extra work yapıyor.
Simulator’da Debug > Color Misaligned Images açıyor. Sarı highlight misaligned image.
Fix’ler:
1. Frame’leri integer-align:
let newFrame = view.frame.integral
view.frame = newFrame2. Scale factor doğru set:
imageView.layer.contentsScale = UIScreen.main.scale3. Image size’larını device resolution’a uygun hazırla.
Bu genelde minor optimization, ama dense UI’da tangible fark.
Gerçek senaryo: tableview lag
Dentii’de brushing history’yi gösteren tableview scroll ederken lag yapıyordu.
Instruments Core Animation:
– FPS: 30-40 (hedef 60)
– Dropped frames: 20%
– Offscreen passes: high
Investigation:
class HistoryCell: UITableViewCell {
func configure(with session: BrushingSession) {
// Avatar image with circular crop
avatarView.layer.cornerRadius = 30
avatarView.layer.masksToBounds = true // Offscreen!
// Drop shadow around entire cell
contentView.layer.shadowRadius = 5
contentView.layer.shadowOpacity = 0.2
// No shadowPath!
}
}Fix’ler:
- Shadow path:
contentView.layer.shadowPath = UIBezierPath(roundedRect: contentView.bounds, cornerRadius: 8).cgPath- Avatar pre-crop: Image’i zaten circular olarak compose ederek asset catalog’a ekle. corner radius’a ihtiyaç yok.
If pre-crop mümkün değil:
avatarView.layer.cornerRadius = 30
avatarView.layer.masksToBounds = true
avatarView.layer.shouldRasterize = true // Cache rendered version
avatarView.layer.rasterizationScale = UIScreen.main.scaleRasterize ile corner clipping sadece bir kez. Re-use.
Result: FPS 55-60, dropped frames %2. Acceptable.
Gerçek senaryo 2: animation janky
CVCrafter’da template picker swipe animation choppy. iPad’de daha kötü.
Instruments gösterdi:
- Render server phase 25ms (frame budget 16ms)
- Heavy CPU during commit phase
Kod:
func animateTransition(...) {
UIView.animate(withDuration: 0.3) {
self.oldView.alpha = 0
self.newView.alpha = 1
// Both views staying on screen during animation
}
}İki view aynı anda, ikisi de blended (alpha changing). GPU her frame blend yapıyor.
Fix:
- Layer tree reduction. Animation sırasında sadece gerekli layer’lar visible.
- Explicit completion:
func animateTransition(...) {
UIView.animate(withDuration: 0.3, animations: {
self.oldView.alpha = 0
self.newView.alpha = 1
}) { _ in
self.oldView.removeFromSuperview() // Clean up
}
}- Transition API kullan:
UIView.transition(from: oldView, to: newView, duration: 0.3, options: .transitionCrossDissolve)iOS optimize ediyor internal olarak.
Result: smooth 60fps animation.
Image-heavy screen optimization
Dentii’de “all brushings” screen’de yüzlerce thumbnail. Scroll janky.
Image loading problem’i. Her cell image load tetikliyor, main thread busy.
Fix: async image loading:
class ImageLoader {
let cache = NSCache<NSString, UIImage>()
func load(url: URL, completion: @escaping (UIImage?) -> Void) {
let key = url.absoluteString as NSString
if let cached = cache.object(forKey: key) {
completion(cached)
return
}
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
guard let data = data, let image = UIImage(data: data) else {
DispatchQueue.main.async { completion(nil) }
return
}
self?.cache.setObject(image, forKey: key)
DispatchQueue.main.async { completion(image) }
}.resume()
}
}Cell’de:
func configure(with session: BrushingSession) {
imageView.image = nil // placeholder
imageLoader.load(url: session.thumbnailURL) { [weak self, currentID = session.id] image in
// Cell reuse check: hâlâ aynı session mi?
guard self?.currentSessionID == currentID else { return }
self?.imageView.image = image
}
}Third-party alternative: SDWebImage, Kingfisher. Production-proven.
SwiftUI performance
SwiftUI rendering model farklı. Modifier chain recursive, view body recompute.
Performance tool’lar:
Instruments SwiftUI template. View body recomputation frequency.
Self._printChanges(): View body içinde call. Hangi dependency değişti, neden re-render.
var body: some View {
let _ = Self._printChanges() // Log changes
return VStack { ... }
}Gereksiz re-render’ları tespit etmek için.
SwiftUI-specific optimizations:
- @State vs @Binding disiplin. Yanlış ownership gereksiz re-render.
- ForEach identify correctly. Wrong id strategy → whole list recomputation.
- EquatableView wrapper. Eşit kaldığında re-render’ı skip et.
- @ViewBuilder minimize state dependency. Small scope ViewBuilder’lar.
General principles
Core Animation debugging öğrendiklerim:
- Profile first, optimize second. Assumption’a göre değil, data’ya göre fix.
- shadowPath her shadow için. Dramatic performance.
- Transparency expensive. Opaque preferred.
- cornerRadius + masksToBounds cautious use. iOS 13+ better ama hâlâ cost var.
- Rasterize sparingly. Static content için yes, dynamic için no.
- Async image loading. Main thread’i kilitleme.
- Recycling correctly. Cell reuse, async operation kontrol et.
Sonuç
Core Animation performance iOS dev’in ileri seviye yeteneği. Instruments ile profiling + common pattern bilgisi + iteration.
60fps smooth experience standard, altı user’ın hissettiği quality loss. Performance profiling her release öncesi disciplin olsun.
Fix’lerin %80’i: shadowPath, opaque backgrounds, async loading, cell reuse. Bu 4 pattern’a dikkat edilirse çoğu performance issue önleniyor.