Section 6/181 menit

6. Profiling dengan Instruments

6. Profiling dengan Instruments

Instruments adalah profiling toolkit Apple yang terintegrasi dengan Xcode. Aksesnya via Xcode → Product → Profile (⌘ + I).

Time Profiler: Cari Bottleneck CPU

swift
// Sebelum profiling, pastikan:
// 1. Build dengan Release configuration (bukan Debug)
// 2. Simulasi real user behavior, bukan edge case
// 3. Run on device fisik (bukan simulator) untuk hasil akurat

// Workflow Time Profiler:
// 1. ⌘ + I → pilih "Time Profiler"
// 2. Record selama 10–30 detik sambil reproduce masalah
// 3. Stop recording
// 4. Lihat "Heaviest Stack Trace"
// 5. Filter: ✓ "Hide system libraries" untuk fokus ke kode kita
// 6. Double-click frame untuk buka kode di Xcode

// Contoh kode yang bakal ketahuan lambat:
// ❌ JSON parsing di main thread
func loadData() {
    let data = try! Data(contentsOf: fileURL)       // blocking I/O
    let items = try! JSONDecoder().decode([Item].self, from: data)  // heavy parsing
    self.items = items  // UI update
}

// ✓ Setelah profiling, fix dengan background processing
func loadData() {
    Task.detached(priority: .userInitiated) {
        let data = try Data(contentsOf: fileURL)
        let items = try JSONDecoder().decode([Item].self, from: data)
        await MainActor.run { self.items = items }
    }
}

Memory Graph Debugger: Cari Retain Cycle

swift
# Di Xcode Debug Navigator (⌘ + 7):
# Saat app running → klik ikon memory graph (segitiga bertumpuk)
# Xcode generate snapshot memori saat itu

# Tanda retain cycle: objek masih ada padahal seharusnya sudah di-deinit
swift
// ❌ Retain cycle klasik: closure capture strong self
class ProfileViewModel {
    var onUpdate: (() -> Void)?
    
    func loadProfile() {
        APIClient.shared.fetchProfile { response in
            self.handleResponse(response)  // ← strong capture!
            self.onUpdate?()               // ← strong capture!
        }
    }
}

// ✓ Fix dengan [weak self]
class ProfileViewModel {
    var onUpdate: (() -> Void)?
    
    func loadProfile() {
        APIClient.shared.fetchProfile { [weak self] response in
            guard let self else { return }
            handleResponse(response)
            onUpdate?()
        }
    }
}

// ✓ Dengan Swift Concurrency: tidak perlu [weak self] pada Task
@MainActor
class ProfileViewModel {
    var profile: Profile?
    
    func loadProfile() {
        Task {
            // self di-capture sebagai weak oleh Swift Concurrency runtime
            // tapi tetap safe karena Task diikat ke lifecycle actor
            let result = try await APIClient.shared.fetchProfile()
            self.profile = result
        }
    }
}

Allocations: Cari Memory Leak & Excessive Allocation

swift
InstrumentsAllocations template:

1. Record sambil navigate bolak-balik ke sebuah screen
2. Gunakan "Generation" feature: 
   Mark Anavigate ke screenMark Bnavigate keluarMark C
3. Lihat objek yang masih ada di Mark C tapi seharusnya sudah hilang
4. Click objek → "Backtrace" untuk lihat di mana dialokasikan

Metrics yang perlu diperhatikan:
- All Allocations: total memory yang pernah dialokasi
- Live Bytes: memory yang sedang aktif digunakan
- # Living: jumlah instance yang masih hidup

Leaks: Auto-detect Memory Leak

swift
Instruments → Leaks template:

Lebih mudah dari Allocations — Instruments otomatis flag memory leak
dengan warna merah saat ditemukan.

Pastikan:
- Record minimal 30 detik
- Buka/tutup screen yang diduga bocor minimal 3x
- Lihat "Leaked Objects" di timeline

Network: Analisis Request & Response

swift
Instruments → Network template:

Menampilkan:
- Semua HTTP/HTTPS request dengan latency
- Upload & download size
- Connection pooling & timing (DNS, TCP connect, TLS, TTFB, transfer)

Berguna untuk:
- Cari request yang unexpectedly slow
- Detect payload yang terlalu besar
- Verify caching behavior