Giriş#
Kullanıcılar düşük frame rate’e, yavaş açılış sürelerine ve yüksek bellek kullanımına karşı acımasızdır. Bu rehberde Xcode Instruments ile performans sorunlarını sistematik olarak nasıl bulacağınızı ve düzelteceğinizi göreceğiz.
Instruments’a Giriş#
Instruments’ı açmak için: Xcode → Product → Profile (⌘I)
Yaygın kullandığım template’ler:
- Time Profiler — CPU darboğazları
- Allocations — Bellek kullanımı ve leak’ler
- Core Animation — Frame drop’ları
- Network — Ağ isteklerinin analizi
1. Frame Drop’larını Tespit Etme#
Hedef: 60 FPS (16.67ms/frame) veya ProMotion için 120 FPS.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // ❌ Main thread'de ağır iş
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(...)
let image = UIImage(contentsOfFile: heavyImagePath) // Disk IO — main thread!
cell.imageView?.image = image
return cell
}
// ✅ Background thread'de yükle
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(...)
Task {
let image = await loadImageAsync(from: heavyImagePath)
await MainActor.run {
cell.imageView?.image = image
}
}
return cell
}
|
2. Bellek Leak’lerini Bulma#
En yaygın bellek leak kaynağı: retain cycle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // ❌ Retain cycle
class ProfileViewController: UIViewController {
var viewModel: ProfileViewModel!
override func viewDidLoad() {
viewModel.onUpdate = {
self.updateUI() // self'i strong olarak tutar!
}
}
}
// ✅ Weak self kullan
override func viewDidLoad() {
viewModel.onUpdate = { [weak self] in
self?.updateUI()
}
}
|
Combine’da da dikkat:
1
2
3
4
5
6
7
8
9
| // ❌ Sink'te strong self
cancellable = publisher.sink { value in
self.process(value)
}
// ✅
cancellable = publisher.sink { [weak self] value in
self?.process(value)
}
|
3. Launch Time Optimizasyonu#
App Store verilerine göre, 2 saniyenin üzerindeki launch time kullanıcı kaybına yol açıyor.
Pre-main süresini kısaltmak için:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // ❌ AppDelegate'de senkron yükleme
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
Database.shared.setup() // 300ms
Analytics.shared.start() // 200ms
RemoteConfig.fetch() // Network!
return true
}
// ✅ Lazy ve async yükleme
func application(...) -> Bool {
Task {
await Analytics.shared.startAsync()
await RemoteConfig.fetchAsync()
}
return true
}
|
SwiftUI’da gereksiz re-render’ları önlemek:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // ❌ Tüm parent yeniden render olduğunda child da render olur
struct ParentView: View {
@StateObject var viewModel = HeavyViewModel()
var body: some View {
ChildView(data: viewModel.data)
}
}
// ✅ Equatable conformance ile optimize et
struct ChildView: View, Equatable {
let data: DisplayData
static func == (lhs: ChildView, rhs: ChildView) -> Bool {
lhs.data == rhs.data
}
var body: some View { ... }
}
|
Core Animation Profiling#
CALayer animasyonlarında offscreen rendering büyük sorun:
1
2
3
4
5
6
7
8
9
10
| // ❌ Pahalı: GPU'da offscreen buffer oluşturur
layer.cornerRadius = 12
layer.masksToBounds = true
layer.shadow... // Aynı anda hem cornerRadius hem shadow
// ✅ UIBezierPath ile manual corner
let maskPath = UIBezierPath(roundedRect: bounds, cornerRadius: 12)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
layer.mask = maskLayer
|
Sonuç Kontrol Listesi#
Performans optimizasyonu bir kere yapılıp bırakılacak bir iş değil, sürekli bir pratik. Instruments’ı her feature development’ında çalıştırın.