Ana Sayfa / Blog / Instruments ile memory leak avı: 3 gerçek senaryo

Instruments ile memory leak avı: 3 gerçek senaryo

Memory leak iOS uygulamaların en sinsi sorunu. Xcode Instruments'in Leaks ve Allocations tool'larıyla gerçek örnekler.

Memory leak production’da uzun süreli kullanılan app’lerde ortaya çıkıyor. User 30 dakika app’te geziniyor, RAM yavaş yavaş dolup app yavaşlıyor veya crash ediyor.

Debug etmek zor çünkü reproducing için specific user flow gerekli. Instruments bu tür sorunları yakalamanın yolu. 3 gerçek senaryoyu anlatacağım: her biri farklı leak pattern’i.

Instruments’ı tanımak

Xcode’da Product > Profile (Cmd+I). App release build’lenip Instruments açılıyor.

Kritik tool’lar:

  • Leaks: Actual memory leak’leri tespit ediyor (reference cycle, etc).
  • Allocations: Hangi object’ler memory tutuyor, live count.
  • Time Profiler: CPU hangi fonksiyona harcanıyor.
  • Energy Log: Battery impact.
  • Core Animation: UI performance.

Memory leak için Leaks + Allocations kombinasyonu en güçlü.

Senaryo 1: Closure retain cycle

Dentii’de TabBar’dan bir view’a navigate ediyordu user. Sonra back’e basıyordu, view dealloc olmuyordu. Her navigation’da bir instance leak.

Instruments’ta:

  1. Leaks açtım
  2. Navigate yaptım, back yaptım (5 kere)
  3. Leaks tool’unda 5 DashboardViewController instance’ı görünüyordu

Allocations cycle görüntüsü:

DashboardViewController
  └─ self.dataFetcher.callback
       └─ captures self (strong reference)

Retain cycle.

Kodda:

class DashboardViewController: UIViewController {
    let dataFetcher = DataFetcher()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        dataFetcher.callback = { result in
            self.handleResult(result)  // BUG: self strong capture
        }
    }
}

Controller dataFetcher’ı tutuyor, dataFetcher callback’inde self’i tutuyor. Cycle.

Fix:

dataFetcher.callback = { [weak self] result in
    self?.handleResult(result)
}

[weak self] ile cycle kırılıyor. Controller dealloc olabiliyor.

Prevention pattern: Her escaping closure’da [weak self] default kullan. Sadece closure view controller’dan önce bitecek ise [unowned self].

Senaryo 2: Timer repeat without invalidation

Snoozio’da background’a çıkıldığında app kullanmaya devam ediyor. 8 saat sonra kullanıcı app’i tekrar açıyor, memory inanılmaz yüksek.

Instruments Allocations:

  1. App açıldığında: 50MB memory
  2. 30 dakika sonra: 150MB
  3. 1 saat sonra: 300MB

Linear büyüme. Timer kaynaklı.

Kodda:

class SleepTracker {
    func start() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            self.checkSleepState()
        }
    }
}

Timer selfi tutuyor, self timer’ı tutmuyor ama timer runloop’ta active. Dealloc edilmiyor. Her start() yeni timer.

Fix:

class SleepTracker {
    private var timer: Timer?
    
    func start() {
        stop()
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.checkSleepState()
        }
    }
    
    func stop() {
        timer?.invalidate()
        timer = nil
    }
    
    deinit {
        stop()
    }
}

Timer reference tutuluyor, invalidate edilebiliyor, weak self ile cycle yok.

Prevention: Scheduled timer’lar her zaman invalidate() ile bitmeli. deinit’te cleanup garanti.

Senaryo 3: Observer registration without removal

CVCrafter’da keyboard notification observer’ı register ediliyordu her screen’de. Ama bazı screen’ler close olunca remove etmiyordu.

Instruments Leaks bunu direct göstermedi (leak değil technically), ama Allocations memory büyümesi.

Observers counter’ı kontrol ettim:

do {
    let token = NotificationCenter.default.addObserver(
        forName: UIResponder.keyboardWillShowNotification,
        object: nil,
        queue: .main
    ) { notification in ... }
}

Token’ı store etmediğim için remove edemiyordum. Her screen keyboard observer ekliyordu, hiçbiri silinmiyordu.

Fix:

class FormViewController: UIViewController {
    private var observers: [NSObjectProtocol] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let token = NotificationCenter.default.addObserver(
            forName: UIResponder.keyboardWillShowNotification,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            self?.handleKeyboard(notification)
        }
        observers.append(token)
    }
    
    deinit {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
    }
}

Token store, deinit’te remove. Observer lifecycle temiz.

Alternative: Swift 5.5+ ile Notification Center async sequence:

for await notification in NotificationCenter.default.notifications(named: .keyboard...) {
    // task cancelled olduğunda observation otomatik stop
}

Instruments workflow

Yaklaşımım:

Step 1: Allocations profile al.

  1. App’i launch et
  2. Initial memory’yi kaydet (baseline)
  3. Leak olacak suspected flow’u execute et (navigate, scroll, whatever)
  4. Initial state’e dön
  5. “Persistent allocation” counter’ını check et

İdeal: counter baseline’a döner. Değerse leak var.

Step 2: Leaks tool’unu çalıştır.

Active leak’leri tespit ediyor. Stack trace gösteriyor.

Step 3: Cycle visualize et.

Allocations’da object type filter’la. Retain cycle graph view’ı.

Step 4: Fix.

Weak/unowned, invalidation, removal.

Step 5: Re-profile.

Fix’in çalışıp çalışmadığını confirm et.

Xcode’da leak detection automation

Instruments manuel, ama Xcode’da da tool var:

Address Sanitizer (Asan): Memory corruption, buffer overflow.

Thread Sanitizer (TSan): Data race, concurrent access.

Undefined Behavior Sanitizer (UBSan): Undefined behavior.

Malloc Stack Logging: Every allocation’s stack trace.

Debug scheme’de bu sanitizer’ları aç. Normal test sırasında leak’leri yakalıyor, Instruments’a gerek kalmadan.

SwiftUI-specific leaks

SwiftUI UIKit’ten farklı memory model’e sahip. View’lar struct, state @StateObject/@ObservableObject.

Common SwiftUI leak:

struct ContentView: View {
    @StateObject var viewModel: ViewModel = {
        let vm = ViewModel()
        vm.onUpdate = {
            // captures vm in closure
            vm.refresh()
        }
        return vm
    }()
}

ViewModel kendi callback’inde kendini capture ediyor.

Fix: [weak vm] pattern.

SwiftUI’da @State objeleri identity korumuyor her re-render’da yenileniyor gibi görünüyor ama reality @StateObject veya @State ile persistent.

Performance impact of leaks

Leak’lerin gerçek impact’i:

  • Memory pressure: 500MB+ ise iOS app’i kill edebiliyor
  • Battery drain: leaked objects’in cleanup attempts
  • Slow performance: garbage collection benzer zombies
  • Crash in low-memory conditions

Production’da leak’ler sessizce etkili. Crash reporter’da “terminated due to memory pressure” görünüyor.

Testing strategy

Memory leak için test suite:

Unit tests: XCTest’te weak reference pattern. Object create, scope’tan çıkar, weak reference nil olmalı.

func testViewModelDeallocates() {
    weak var viewModel: ViewModel?
    
    autoreleasepool {
        let vm = ViewModel()
        viewModel = vm
        vm.start()
    }
    
    XCTAssertNil(viewModel, "ViewModel should be deallocated")
}

Integration tests: Navigate flow, memory check. Before/after comparison.

Instruments as part of CI: Advanced. Automated profiling release candidate’lar için.

When to profile

Her proje’de milestone’larda:

  • Pre-release: Release build’i Instruments ile profile et. Baseline memory, peak memory, leak count.
  • After major feature: Yeni büyük feature eklediysen memory impact check.
  • User-reported performance issues: “App yavaş/çok RAM kullanıyor” feedback.
  • Annual health check: Yıl’da bir tüm app genel performance review.

Sonuç

Memory leak iOS app’in sinsi düşmanı. Instruments Leaks + Allocations ikilisi ana araç.

3 yaygın pattern: closure retain cycle, timer/observer cleanup eksik, third-party SDK lifecycle. Bu 3’e dikkat ediyorsan çoğu leak’i önlersin.

Weak/unowned default mentality, invalidation discipline, notification removal. Bu pratikler migration kolay.

Ability to use Instruments iOS developer’ın fundamental skill’i. Yılda 1-2 deep dive session yapmak bile big impact.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç