Callback — apa itu, fungsi panggilan balik dan cara kerjanya

Penulis: IT Sectr Diterbitkan: 2026-03-17 Waktu membaca: 11 mnt

Callback — adalah fungsi yang diteruskan ke fungsi lain sebagai argumen dan dijalankan setelah operasi asinkron selesai. Dalam pengembangan mobile, callback digunakan untuk memproses hasil permintaan jaringan, bekerja dengan database, dan animasi. Menurut Apple Documentation (2025), closure di Swift adalah bentuk utama callback dan digunakan di URLSession, GCD, dan Combine. Di Android, callback diimplementasikan melalui antarmuka, lambda Kotlin, dan ListenableFuture.

Poin Utama

  • Callback — fungsi panggilan balik yang diteruskan sebagai argumen untuk eksekusi asinkron.
  • Swift menggunakan closure dengan kata kunci @escaping untuk callback.
  • Kotlin menerapkan ekspresi lambda dan fungsi tingkat tinggi untuk callback.
  • Retain cycle — kebocoran memori saat menangkap self di callback pada iOS.
  • Callback Hell — masalah callback bersarang, diselesaikan melalui async/await dan coroutine.

Apa itu Callback?

Callback (fungsi panggilan balik) — adalah kode yang dapat dieksekusi yang diteruskan ke fungsi lain dan dipanggil setelah tindakan tertentu selesai. Dalam pengembangan mobile, callback adalah mekanisme fundamental pemrograman asinkron, memungkinkan reaksi terhadap penyelesaian permintaan jaringan, timer, animasi, dan operasi input/output tanpa memblokir thread utama. Swift dan Kotlin menyediakan konstruksi sintaksis bawaan untuk membuat callback — closure dan lambda.

Prinsip Kerja Callback

Fungsi tingkat tinggi menerima fungsi lain sebagai parameter dan memanggilnya setelah menjalankan logika utamanya. Aliran kontrol kembali ke pemanggil melalui callback, itulah asal namanya. Di iOS, callback diterapkan di UIKit (animasi UIView.animate), Foundation (URLSession.dataTask), dan Combine (sink). Di Android, callback digunakan di View.OnClickListener, Retrofit Callback, dan Room DAO. API modern semakin banyak mengganti callback dengan async/await atau coroutine, tetapi pemahaman tentang callback diperlukan untuk bekerja dengan kode lama dan API tingkat rendah.

Callback Sinkron dan Asinkron

Callback bisa sinkron (dipanggil segera di dalam fungsi) dan asinkron (dipanggil nanti dari thread atau antrian lain). Callback sinkron digunakan untuk pengurutan (komparator) dan penelusuran koleksi. Callback asinkron diterapkan untuk permintaan jaringan, pembacaan file, dan bekerja dengan sensor. Perbedaan ini penting untuk memahami threading: callback sinkron dijalankan di thread yang sama, callback asinkron — di thread yang ditentukan oleh dispatcher (DispatchQueue di iOS, Dispatchers di Kotlin).

Bagaimana Callback bekerja di iOS dan Android?

Mekanisme callback di kedua platform didasarkan pada prinsip yang sama: fungsi diteruskan sebagai objek kelas satu dan disimpan hingga saat eksekusi. Namun, implementasinya berbeda karena paradigma bahasa yang berbeda. Di iOS, callback adalah closure yang menangkap variabel dari konteks sekitarnya. Di Android, callback paling sering diimplementasikan melalui kelas anonim atau ekspresi lambda Kotlin, yang dikompilasi menjadi FunctionalInterface.

Siklus Hidup Callback di iOS

Saat fungsi asinkron dipanggil, closure disimpan di heap bersama dengan variabel yang ditangkap. Ketika operasi selesai, sistem GCD atau OperationQueue menempatkan callback di antrian yang sesuai (main queue atau background queue). Setelah eksekusi, callback dihapus dari memori jika tidak ada referensi kuat. Capture list ([weak self]) mencegah objek dipertahankan setelah dealokasi. Tanpa capture list, terjadi retain cycle di mana objek dan callback saling merujuk.

swift
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        completion(.success(data))
    }
    task.resume()
}

// Penggunaan dengan [weak self]
fetchData { [weak self] result in
    guard let self else { return }
    switch result {
    case .success(let data):
        self.updateUI(data)
    case .failure(let error):
        self.showError(error)
    }
}

Siklus Hidup Callback di Android

Di Android, callback diteruskan melalui antarmuka atau lambda. Saat menjalankan operasi asinkron melalui ExecutorService atau coroutine, callback disimpan di memori hingga pekerjaan latar belakang selesai. Lambda Kotlin dikompilasi menjadi kelas anonim yang menangkap variabel eksternal. Tidak adanya referensi lemah di JVM memerlukan pengelolaan manual: mereset callback di onDestroy() atau membatalkan coroutine melalui Job.cancel(). ViewModel dan LiveData menyelesaikan masalah ini di tingkat komponen arsitektur.

kotlin
interface Callback<T> {
    fun onSuccess(data: T)
    fun onError(error: Throwable)
}

class Repository {
    fun loadData(callback: Callback<List<User>>) {
        thread {
            try {
                val result = api.fetchUsers()
                runOnUiThread { callback.onSuccess(result) }
            } catch (e: Exception) {
                runOnUiThread { callback.onError(e) }
            }
        }
    }
}

// Penggunaan dengan lambda
repository.loadData(object : Callback<List<User>> {
    override fun onSuccess(data: List<User>) { showUsers(data) }
    override fun onError(error: Throwable) { showError(error.message) }
})

Sintaks Callback di Swift dan Kotlin

Sintaks callback ditentukan oleh kemampuan bahasa untuk bekerja dengan fungsi sebagai objek kelas satu. Di Swift, closure memiliki sintaks ringkas dengan nama argumen otomatis ($0, $1). Di Kotlin, lambda juga mendukung it untuk argumen tunggal. Perbedaan muncul dalam penanganan penangkapan variabel (capture list di Swift vs referensi yang dapat diubah di Kotlin) dan tipifikasi (Result<Success, Failure> vs Result<T>).

Callback di Swift: closure

Closure Swift adalah blok kode mandiri yang dapat diteruskan dan digunakan di fungsi lain. Closure bisa bersifat global (bernama), bersarang, dan expression-level. @escaping menandai closure yang akan dieksekusi setelah kembali dari fungsi — ini adalah persyaratan wajib untuk callback asinkron. Tanpa @escaping, closure hanya dapat dieksekusi di dalam tubuh fungsi. Trailing closure syntax memungkinkan penerusan closure setelah tanda kurung bulat: fetchData { result in ... }.

swift
typealias NetworkResult = (Result<[String: Any], Error>) -> Void

func performRequest(
    url: URL,
    then handler: @escaping NetworkResult
) {
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        handler(Result {
            guard let json = try JSONSerialization.jsonObject(with: data)
            else { throw NetworkError.invalidData }
            return json as! [String: Any]
        })
    }
    task.resume()
}

performRequest(url: url) { result in
    switch result {
    case .success(let json): process(json)
    case .failure(let error): log(error.localizedDescription)
    }
}

Callback di Kotlin: lambda dan fungsi tingkat tinggi

Kotlin mendukung fungsi tingkat tinggi yang menerima fungsi lain sebagai parameter. Callback di Kotlin diteruskan melalui parameter bertipe (T) -> Unit atau (T) -> R untuk nilai kembalian. Fungsi suspend dari coroutine Kotlin mengganti callback dengan kode sekuensial, tetapi callback tetap ada di API yang kompatibel dengan Java dan Android SDK (View.setOnClickListener, TextWatcher). Lambda Kotlin secara otomatis menangkap variabel val, variabel var memerlukan pembungkus yang dapat diubah.

kotlin
fun <T, R> processWithCallback(
    input: T,
    transform: (T) -> R,
    onResult: (R) -> Unit
) {
    thread {
        val result = transform(input)
        runOnUiThread { onResult(result) }
    }
}

// Contoh dengan lambda
processWithCallback(
    input = "Hello",
    transform = { it.length },
    onResult = { length ->
        textView.text = "Length: $length"
    }
)

Retain cycles dan kebocoran memori di Callback

Retain cycle — situasi di mana dua objek mempertahankan referensi kuat satu sama lain, mencegah pelepasan memori oleh garbage collector. Di Swift, retain cycle terjadi ketika viewController menangkap closure, dan closure menangkap self. Di Kotlin/Java, kebocoran terjadi ketika Activity meneruskan kelas dalam atau lambda ke operasi latar belakang yang panjang. Menurut WWDC Session 10216 (2024), pengelolaan closure yang tidak benar adalah penyebab kebocoran memori ketiga paling umum di aplikasi iOS.

Retain cycles di Swift

Swift menggunakan Automatic Reference Counting (ARC), yang membebaskan objek saat penghitung referensi dinolkan. Capture list [weak self] atau [unowned self] di closure mencegah retain cycle. weak self membuat referensi opsional yang menjadi nil saat dealokasi objek. unowned self mengasumsikan objek hidup lebih lama dari closure — pelanggaran asumsi ini menyebabkan crash. Penggunaan weak self sebagai opsi default yang aman direkomendasikan.

swift
class DataController {
    var onDataUpdate: ((String) -> Void)?

    func setupCallback() {
        // Retain cycle!
        onDataUpdate = { text in
            self.process(text)
        }

        // Diperbaiki: [weak self]
        onDataUpdate = { [weak self] text in
            guard let self else { return }
            self.process(text)
        }
    }

    func process(_ input: String) { }
}

Kebocoran memori di Android

Di Android, kebocoran callback terjadi ketika Activity atau Fragment meneruskan listener ke komponen singleton (misalnya, EventBus atau Service). WeakReference memungkinkan garbage collector membebaskan Activity, meskipun ada referensi lemah ke sana. Komponen Lifecycle-aware (LiveData, Flow) menyelesaikan masalah secara otomatis. Lambda Kotlin yang menangkap konteks Activity juga dapat menyebabkan kebocoran: lambda secara implisit menyimpan referensi ke this.

kotlin
class SafeCallbackManager {
    private val listeners = mutableListOf<WeakReference<(String) -> Unit>>()

    fun addListener(callback: (String) -> Unit) {
        listeners.add(WeakReference(callback))
    }

    fun notifyAll(data: String) {
        val iterator = listeners.iterator()
        while (iterator.hasNext()) {
            val ref = iterator.next().get()
            if (ref != null) ref(data)
            else iterator.remove()
        }
    }
}

// Penggunaan di Fragment
manager.addListener { result ->
    // WeakReference tidak menahan Fragment
    updateUI(result)
}

Callback Hell dan cara mengatasinya

Callback Hell (juga dikenal sebagai Piramida Bencana) — situasi di mana banyak callback bersarang menciptakan struktur kode bersarang dalam yang sulit dibaca dan di-debug. Setiap langkah berikutnya memerlukan penantian penyelesaian langkah sebelumnya, yang menyebabkan 5-10 tingkat sarang. Masalah ini khas untuk operasi asinkron sekuensial: memuat data → parsing → menyimpan ke database → memperbarui UI.

Solusi di Swift: async/await

Swift 5.5 memperkenalkan fungsi asinkron (async/await) yang memungkinkan penulisan kode asinkron secara sekuensial. AsyncSequence dan AsyncStream menggantikan iterasi berbasis callback. Framework Combine menyediakan operator flatMap, merge, combineLatest untuk komposisi aliran asinkron tanpa sarang. Namun, callback tetap diperlukan untuk bekerja dengan API Objective-C dan pustaka pihak ketiga tanpa dukungan async.

swift
// Callback bersarang — Callback Hell
loginUser(credentials) { user in
    fetchProfile(user.id) { profile in
        downloadAvatar(profile.avatarUrl) { image in
            cacheImage(image) { success in
                updateUI(user, profile, image)
            }
        }
    }
}

// async/await — solusi
func loadUserExperience() async throws {
    let user = try await loginUser(credentials)
    let profile = try await fetchProfile(user.id)
    let image = try await downloadAvatar(profile.avatarUrl)
    try await cacheImage(image)
    updateUI(user, profile, image)
}

Solusi di Kotlin: coroutine dan Flow

Coroutine Kotlin mengganti callback dengan fungsi suspend dengan eksekusi sekuensial. Flow menyediakan aliran cold dengan operator map, flatMapConcat, combine. CoroutineScope memungkinkan pembatalan semua coroutine yang diluncurkan saat komponen dihancurkan. Room, Retrofit, dan pustaka Jetpack lainnya memiliki dukungan bawaan untuk fungsi suspend, menghilangkan kebutuhan callback untuk operasi standar.

kotlin
// Callback sekuensial — Callback Hell
api.login(credentials) { user ->
    api.fetchProfile(user.id) { profile ->
        api.download(profile.avatarUrl) { bytes ->
            file.save(bytes) { result ->
                textView.text = result.toString()
            }
        }
    }
}

// Coroutine — solusi
suspend fun loadUserData() {
    val user = withContext(Dispatchers.IO) { api.login(credentials) }
    val profile = withContext(Dispatchers.IO) { api.fetchProfile(user.id) }
    val bytes = withContext(Dispatchers.IO) { api.download(profile.avatarUrl) }
    withContext(Dispatchers.IO) { file.save(bytes) }
    textView.text = "Done"
}

Callback vs Delegate: mana yang harus dipilih?

Callback dan Delegate — dua pendekatan untuk notifikasi asinkron, dan pilihan di antara mereka tergantung pada persyaratan arsitektur. Callback cocok untuk operasi satu kali dengan satu hasil. Delegate dirancang untuk banyak peristiwa dengan tanda tangan metode yang berbeda. Apple merekomendasikan delegate untuk protokol kompleks dengan banyak metode, callback — untuk closure sederhana dengan satu hasil. Di Android, callback menggantikan delegate di sebagian besar kasus karena dukungan lambda.

Kapan memilih Callback

Callback optimal untuk operasi dengan satu hasil: permintaan jaringan, pembacaan file, animasi dengan blok completion. Keuntungan: sintaks ringkas, tidak ada protokol terpisah, penangkapan konteks langsung. Kekurangan: kompleksitas dengan banyak hasil (kemajuan, jeda, pembatalan), ketidakmampuan pengiriman ganda (jika callback dapat dipanggil lebih dari sekali — gunakan publisher).

Kapan memilih Delegate

Delegate cocok untuk protokol dengan banyak metode wajib dan opsional: UITableViewDelegate, CLLocationManagerDelegate, koneksi Bluetooth. Keuntungan: tipifikasi jelas setiap metode, dokumentasi melalui protokol, dukungan metode opsional melalui @objc optional. Kekurangan: kode boilerplate, referensi lemah wajib ke delegate (weak var delegate), kompleksitas dalam penangkapan konteks.

Pertanyaan yang Sering Diajukan

Apa perbedaan antara callback dan fungsi tingkat tinggi?

Callback adalah kasus khusus dari fungsi tingkat tinggi. Fungsi tingkat tinggi menerima fungsi lain sebagai argumen atau mengembalikannya. Callback adalah fungsi yang diteruskan khusus untuk eksekusi asinkron setelah operasi selesai. Semua callback diimplementasikan melalui fungsi tingkat tinggi, tetapi tidak setiap fungsi tingkat tinggi adalah callback.

Bisakah callback dipanggil beberapa kali?

Menurut konvensi, callback harus dipanggil tepat satu kali — baik success maupun failure. Pemanggilan ganda dari callback yang sama dianggap sebagai kesalahan desain. Untuk banyak peristiwa (kemajuan, aliran data) gunakan Observable, Publisher, atau Flow — mereka mendukung emisi nilai ganda. Beberapa API melanggar aturan ini, yang menyebabkan bug yang sulit dilacak.

Apa itu trailing closure di Swift?

Trailing closure — gula sintaksis Swift yang memungkinkan penerusan closure setelah tanda kurung bulat panggilan fungsi. Jika fungsi menerima closure sebagai argumen terakhir, ia dapat ditempatkan di luar tanda kurung: fetchData { result in ... }. Untuk beberapa closure, trailing closure hanya diterapkan pada yang terakhir, sisanya dinamai di dalam tanda kurung. Ini meningkatkan keterbacaan API berbasis callback.

Bagaimana cara menghindari kebocoran memori saat callback di Android?

Gunakan WeakReference untuk listener berumur panjang, batalkan coroutine melalui Job.cancel() di onDestroy(), terapkan lifecycleScope untuk pembatalan otomatis. ViewModel + LiveData/Flow menyelesaikan masalah di tingkat arsitektur. Hindari meneruskan konteks Activity ke callback statis — gunakan konteks Application. Lambda Kotlin secara implisit menangkap this, periksa melalui memory profiler.

Akankah async/await sepenuhnya menggantikan callback?

Async/await menggantikan callback untuk kode asinkron sekuensial, tetapi tidak untuk arsitektur event-driven. Callback tetap ada di API sistem (View.OnClickListener, delegasi URLSession), panggilan balik dengan kemajuan, dan pustaka pihak ketiga. Penggantian total tidak mungkin karena kompatibilitas mundur. Strategi modern adalah menggunakan async/await dengan pembungkus callback (continuation di Swift, suspendCancellableCoroutine di Kotlin).

Kesimpulan

  • Callback — fungsi panggilan balik yang diteruskan sebagai argumen untuk eksekusi asinkron setelah operasi selesai.
  • Swift mengimplementasikan callback melalui closure dengan @escaping, capture list [weak self], dan trailing closure syntax.
  • Kotlin menggunakan lambda, fungsi tingkat tinggi, dan fungsi suspend coroutine untuk asinkronitas.
  • Retain cycle di iOS dicegah dengan capture list; di Android — dengan WeakReference dan komponen Lifecycle-aware.
  • Callback Hell diselesaikan melalui async/await di Swift dan coroutine dengan Flow di Kotlin.
  • Delegate lebih disukai daripada callback untuk protokol dengan banyak metode; callback — untuk operasi satu kali.
  • Gunakan callback untuk operasi asinkron sederhana, async/await untuk rantai sekuensial, delegate untuk banyak peristiwa.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga