Section 6/131 menit

6. withObservationTracking — Observasi Manual

6. withObservationTracking — Observasi Manual

withObservationTracking berguna ketika kita butuh observasi di luar SwiftUI — misalnya di UIKit, di layer business logic, atau untuk testing.

swift
import Observation

@Observable
final class CounterModel {
    var count: Int = 0
    var label: String = "Start"
}

// Observasi manual — cocok untuk UIKit atau non-SwiftUI context
func setupManualObservation(model: CounterModel, label: UILabel) {
    func observe() {
        withObservationTracking {
            // Blok ini dijalankan SEGERA dan mencatat dependency.
            // Semua property yang diakses di sini akan di-track.
            label.text = "\(model.count)"  // → track: count
        } onChange: {
            // Blok ini dipanggil SATU KALI ketika dependency berubah.
            // Setelah dipanggil, tracking berhenti — harus daftar ulang.
            // Ini adalah "fire-once" observer — berbeda dengan Combine publisher.
            DispatchQueue.main.async {
                observe()  // Daftar ulang untuk melanjutkan tracking
            }
        }
    }
    observe()
}

Pola rekursif di atas adalah idiom standar untuk continuous observation dengan withObservationTracking. Setiap kali dependency berubah, kita daftar ulang observasi — sehingga tracking terus berjalan.

Integrasi UIKit dengan withObservationTracking

swift
// UIKit ViewController yang mengobservasi @Observable model
final class CounterViewController: UIViewController {
    private let model = CounterModel()
    private var observationTask: Task<Void, Never>?

    private let countLabel = UILabel()
    private let incrementButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        startObserving()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        observationTask?.cancel()
    }

    private func startObserving() {
        // Bungkus dalam Task agar bisa di-cancel saat view hilang.
        // Gunakan AsyncStream untuk mengubah "fire-once" withObservationTracking
        // menjadi continuous async sequence.
        observationTask = Task { @MainActor in
            let stream = AsyncStream<Void> { continuation in
                func scheduleObservation() {
                    withObservationTracking {
                        // Akses property yang ingin di-track
                        _ = self.model.count
                        _ = self.model.label
                    } onChange: {
                        continuation.yield(())
                        // Jadwal observasi ulang setelah yield
                        DispatchQueue.main.async { scheduleObservation() }
                    }
                }
                scheduleObservation()
                continuation.onTermination = { _ in }
            }

            for await _ in stream {
                guard !Task.isCancelled else { break }
                // Update UI saat ada perubahan
                countLabel.text = "\(model.count)"
                title = model.label
            }
        }
    }

    @objc private func handleIncrement() {
        model.count += 1
    }

    private func setupUI() {
        // ... setup constraints
    }
}