Section 15/161 menit
15. Real Use Cases
15. Real Use Cases
Use Case 1: Menemukan Memory Leak di UIKit
Skenario: Aplikasi mengonsumsi RAM terus bertambah setelah navigasi bolak-balik ke halaman detail. Instruments menunjukkan leak tapi tidak jelas dari mana.
swift
// Kode yang diduga mengalami leak
final class DetailViewController: UIViewController {
var viewModel: DetailViewModel?
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// Bug: closure capture strong self tanpa [weak self]
viewModel?.dataPublisher
.sink { data in
self.updateUI(with: data) // ← strong reference cycle!
}
.store(in: &cancellables)
}
}
swift
# Langkah 1: Buka Instruments Leaks → konfirmasi DetailViewController leak
# Langkah 2: Pasang breakpoint di deinit
(lldb) breakpoint set --func-regex "DetailViewController.*deinit"
# Langkah 3: Navigasi ke detail, kembali ke list
# Jika breakpoint TIDAK dipicu → confirm object tidak di-deallocate (leak)
# Langkah 4: Cek retain count
(lldb) expr import ObjectiveC
(lldb) expr CFGetRetainCount(myViewController)
# Output: retain count tinggi → ada strong reference yang tidak dilepas
# Langkah 5: Cari siapa yang retain object
(lldb) watchpoint set variable myViewController --watch write
# Setiap kali retain count berubah, LLDB berhenti dan tunjukkan stack trace
# Langkah 6: Identifikasi leaking subscriber
(lldb) po myViewController.cancellables
# Lihat subscriber — apakah ada yang tidak di-cancel
# Langkah 7: Fix di kode
swift
// Setelah menemukan bug via LLDB, fix dengan [weak self]
viewModel?.dataPublisher
.sink { [weak self] data in
self?.updateUI(with: data)
}
.store(in: &cancellables)
// Verifikasi fix: breakpoint di deinit sekarang dipicu saat pop ViewController
Use Case 2: Debugging Race Condition di Swift Concurrency
Skenario: App kadang-kadang crash dengan "Fatal error: Unexpectedly found nil" di baris yang seharusnya aman. Crash tidak konsisten dan sulit direproduksi.
swift
// Kode yang diduga race condition
final class CacheManager {
private var cache: [String: Data] = [:] // ⚠️ tidak thread-safe
func store(_ data: Data, for key: String) {
cache[key] = data // bisa race dengan read
}
func retrieve(for key: String) -> Data? {
return cache[key] // bisa race dengan write
}
}
// Dipanggil dari multiple async contexts
Task {
await networkManager.fetchImage(url: url1) { data in
cacheManager.store(data, for: "img1") // thread A
}
}
Task {
await networkManager.fetchImage(url: url2) { data in
cacheManager.store(data, for: "img2") // thread B — race!
}
}
swift
# Aktifkan Thread Sanitizer di Edit Scheme → Run → Diagnostics
# Jalankan app — TSan akan mendeteksi race:
# WARNING: ThreadSanitizer: Swift access race on address 0x...
# Write of size 8 at 0x... by thread T3:
# #0 Dictionary._rawIndex(forKey:) CacheManager.swift:8
#
# Previous read of size 8 at 0x... by thread T2:
# #0 Dictionary.subscript.getter CacheManager.swift:13
# Di LLDB saat TSan pause:
(lldb) bt all → lihat stack trace kedua threads yang race
(lldb) f 0 → pergi ke frame crash
(lldb) fr v → lihat state variabel
(lldb) po cacheManager.cache → inspect cache state
# Dari info LLDB, konfirmasi: dua thread mengakses cache bersamaan
# Fix: gunakan actor untuk thread safety
swift
// Fix menggunakan actor
actor CacheManager {
private var cache: [String: Data] = [:]
func store(_ data: Data, for key: String) {
cache[key] = data // actor isolation: hanya satu akses dalam satu waktu
}
func retrieve(for key: String) -> Data? {
return cache[key]
}
}
// Penggunaan — sekarang memerlukan await
let data = await cacheManager.retrieve(for: "img1")
Use Case 3: Debug Parsing Bug dengan Conditional Breakpoint
Skenario: JSON parser menghasilkan data salah untuk beberapa user, tapi tidak semua. Log sudah ada tapi terlalu banyak dan susah di-filter.
swift
// Parser yang dicurigai bermasalah
struct UserParser {
func parse(from json: [String: Any]) throws -> User {
guard let id = json["id"] as? String else {
throw ParserError.missingField("id")
}
guard let name = json["name"] as? String else {
throw ParserError.missingField("name")
}
// Bug diduga di sini: logic priority yang salah
let rawPriority = json["priority"] as? Int ?? 0
let priority = Priority(rawValue: rawPriority) ?? .medium
return User(id: id, name: name, priority: priority)
}
}
swift
# Skenario: ada 10.000 users di database, hanya beberapa yang salah priority
# TANPA conditional breakpoint: breakpoint di parse() → berhenti 10.000 kali
# DENGAN conditional breakpoint:
# Berhenti hanya jika rawPriority memiliki nilai tidak terduga
(lldb) breakpoint set --file UserParser.swift --line 12 \
--condition "rawPriority != 0 && rawPriority != 1 && rawPriority != 2"
# Ketika berhenti, inspect:
(lldb) po json
# Output: {"id": "usr_999", "name": "Budi", "priority": 99}
# Terlihat: ada data dengan priority = 99 yang tidak valid di enum
# Modifikasi runtime untuk test fix hipotesis:
(lldb) expr let $safePriority = min(rawPriority, 2)
(lldb) po Priority(rawValue: $safePriority)
# Output: Optional(Priority.high) ← ini yang benar
# Lanjutkan execution dengan nilai yang diperbaiki:
(lldb) expr rawPriority = 2
(lldb) continue
# Setelah konfirmasi hipotesis, fix di kode:
swift
// Fix: clamp nilai priority ke range yang valid
let rawPriority = (json["priority"] as? Int ?? 0).clamped(to: 0...2)
let priority = Priority(rawValue: rawPriority) ?? .medium
Use Case 4: Debugging Crash di Production dengan dSYM
Skenario: Crash report dari Crashlytics/Firebase menunjukkan crash di framework code tanpa nama fungsi yang jelas.
swift
Thread 1 Crashed:
0 MyApp 0x0000000104a3c210 0x104a00000 + 246288
1 MyApp 0x0000000104a3b8f4 0x104a00000 + 243956
2 MyApp 0x0000000104a12300 0x104a00000 + 75552
swift
# Langkah 1: Symbolicate dengan atos (command line)
xcrun atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-arch arm64 \
-l 0x104a00000 \
0x0000000104a3c210
# Output:
# NetworkManager.fetchUser(id:) (in MyApp) (NetworkManager.swift:87)
# Langkah 2: Reproduce dengan LLDB untuk analisis lebih dalam
# Buka crash report di Xcode: Window → Devices → lihat crash log
# Xcode otomatis symbolicate jika dSYM tersedia
# Langkah 3: Reproduce scenario di debug build
(lldb) breakpoint set --file NetworkManager.swift --line 87
# Pasang conditional breakpoint untuk reproduce kondisi crash
# Langkah 4: Di breakpoint, inspect state
(lldb) po self
(lldb) fr v
(lldb) po urlSession
# Konfirmasi: urlSession = nil (deallocated sebelum callback)
# Root cause: URLSession di-deallocate sebelum task selesai
swift
// Fix: pastikan URLSession retained selama task berjalan
final class NetworkManager {
private let session: URLSession // disimpan sebagai stored property
init() {
self.session = URLSession(configuration: .default) // tidak ephemeral
}
func fetchUser(id: String) async throws -> User {
let (data, _) = try await session.data(from: userURL(id: id))
return try JSONDecoder().decode(User.self, from: data)
}
}