Callback — ay isang function na ipinapasa sa ibang function bilang argumento at isinasagawa pagkatapos makumpleto ang isang asynchronous na operasyon. Sa mobile development, ang callback ay ginagamit para sa pagproseso ng mga resulta ng network request, pagtatrabaho sa mga database, at mga animation. Ayon sa Apple Documentation (2025), ang mga closure sa Swift ay ang pangunahing anyo ng callback at ginagamit sa URLSession, GCD, at Combine. Sa Android, ang callback ay ipinapatupad sa pamamagitan ng mga interface, Kotlin lambda, at ListenableFuture.
Mga Pangunahing Punto
Callback (function ng tawag pabalik) — ay executable na code na ipinapasa sa ibang function at tinatawag pagkatapos makumpleto ang isang partikular na aksyon. Sa mobile development, ang callback ay ang pangunahing mekanismo ng asynchronous programming, na nagbibigay-daan sa pagtugon sa pagkumpleto ng mga network request, timer, animation, at input/output operations nang hindi hinaharangan ang main thread. Ang Swift at Kotlin ay nagbibigay ng mga built-in na syntactic construction para sa paglikha ng callback — mga closure at lambda ayon sa pagkakabanggit.
Ang higher-order function ay tumatanggap ng ibang function bilang parameter at tinatawag ito pagkatapos isagawa ang pangunahing lohika nito. Ang daloy ng kontrol ay bumabalik sa caller sa pamamagitan ng callback, kaya ang pangalan. Sa iOS, ang callback ay inilalapat sa UIKit (mga animation ng UIView.animate), Foundation (URLSession.dataTask), at Combine (sink). Sa Android, ang callback ay ginagamit sa View.OnClickListener, Retrofit Callback, at Room DAO. Ang mga modernong API ay lalong pinapalitan ang callback ng async/await o coroutine, ngunit ang pag-unawa sa callback ay kinakailangan para sa pagtatrabaho sa legacy code at low-level na API.
Ang callback ay maaaring synchronous (tinatawag kaagad sa loob ng function) at asynchronous (tinatawag mamaya mula sa ibang thread o queue). Ang synchronous callback ay ginagamit para sa pag-uuri (mga comparator) at pagtawid sa mga koleksyon. Asynchronous callback ay inilalapat para sa mga network request, pagbabasa ng file, at pagtatrabaho sa mga sensor. Ang pagkakaiba ay kritikal para sa pag-unawa sa threading: ang synchronous callback ay isinasagawa sa parehong thread, ang asynchronous — sa thread na tinutukoy ng dispatcher (DispatchQueue sa iOS, Dispatchers sa Kotlin).
Ang mekanismo ng callback sa parehong platform ay batay sa parehong prinsipyo: ang function ay ipinapasa bilang first-class object at iniimbak hanggang sa oras ng pagpapatupad. Gayunpaman, ang mga implementasyon ay naiiba dahil sa iba't ibang paradigma ng wika. Sa iOS, ang callback ay isang closure na kumukuha ng mga variable mula sa nakapaligid na konteksto. Sa Android, ang callback ay kadalasang ipinapatupad sa pamamagitan ng mga anonymous class o Kotlin lambda expression, na na-compile sa FunctionalInterface.
Kapag tinawag ang isang asynchronous function, ang closure ay iniimbak sa heap kasama ang mga nakuhang variable. Kapag nakumpleto ang operasyon, ang GCD o OperationQueue system ay naglalagay ng callback sa naaangkop na queue (main queue o background queue). Pagkatapos ng pagpapatupad, ang callback ay tinatanggal mula sa memory kung walang malakas na reference. Capture list ([weak self]) ay pumipigil sa pagpapanatili ng object pagkatapos ng deallocation. Kung walang capture list, magkakaroon ng retain cycle kung saan ang object at callback ay nagre-reference sa isa't isa.
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()
}
// Paggamit na may [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)
}
}
Sa Android, ang callback ay ipinapasa sa pamamagitan ng interface o lambda. Kapag nagsasagawa ng asynchronous operation sa pamamagitan ng ExecutorService o coroutine, ang callback ay iniimbak sa memory hanggang sa makumpleto ang background work. Kotlin lambda ay na-compile sa mga anonymous class na kumukuha ng mga panlabas na variable. Ang kawalan ng mahinang reference sa JVM ay nangangailangan ng manu-manong pamamahala: pag-reset ng callback sa onDestroy() o pagkansela ng coroutine sa pamamagitan ng Job.cancel(). Ang ViewModel at LiveData ay nilulutas ang problemang ito sa antas ng architectural component.
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) }
}
}
}
}
// Paggamit na may lambda
repository.loadData(object : Callback<List<User>> {
override fun onSuccess(data: List<User>) { showUsers(data) }
override fun onError(error: Throwable) { showError(error.message) }
})
Ang syntax ng callback ay tinutukoy ng mga kakayahan ng wika sa pagtatrabaho sa mga function bilang first-class object. Sa Swift, ang mga closure ay may maikling syntax na may mga awtomatikong pangalan ng argumento ($0, $1). Sa Kotlin, ang lambda ay sumusuporta rin sa it para sa iisang argumento. Ang mga pagkakaiba ay lumalabas sa paghawak ng pagkuha ng variable (capture list sa Swift vs mutable reference sa Kotlin) at tipifikasi (Result<Success, Failure> vs Result<T>).
Ang Swift closure ay isang self-contained block ng code na maaaring ipasa at gamitin sa ibang function. Ang mga closure ay maaaring global (pinangalanan), nested, at expression-level. @escaping ay nagmamarka ng closure na isasagawa pagkatapos bumalik mula sa function — ito ay isang mandatoryong kinakailangan para sa asynchronous callback. Kung walang @escaping, ang closure ay maaari lamang isagawa sa loob ng body ng function. Ang trailing closure syntax ay nagpapahintulot sa pagpasa ng closure pagkatapos ng mga round bracket: fetchData { result in ... }.
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)
}
}
Ang Kotlin ay sumusuporta sa higher-order function na tumatanggap ng ibang function bilang parameter. Callback sa Kotlin ay ipinapasa sa pamamagitan ng parameter ng uri (T) -> Unit o (T) -> R para sa return value. Ang suspend function ng Kotlin coroutine ay pinapalitan ang callback ng sequential code, ngunit ang callback ay nananatili sa Java-compatible API at Android SDK (View.setOnClickListener, TextWatcher). Ang Kotlin lambda ay awtomatikong kumukuha ng val variable, ang var variable ay nangangailangan ng mutable wrapper.
fun <T, R> processWithCallback(
input: T,
transform: (T) -> R,
onResult: (R) -> Unit
) {
thread {
val result = transform(input)
runOnUiThread { onResult(result) }
}
}
// Halimbawa na may lambda
processWithCallback(
input = "Hello",
transform = { it.length },
onResult = { length ->
textView.text = "Length: $length"
}
)
Retain cycle — sitwasyon kung saan ang dalawang object ay nagpapanatili ng malakas na reference sa isa't isa, na pumipigil sa kanilang pagpapalaya ng garbage collector. Sa Swift, ang retain cycle ay nangyayari kapag ang viewController ay kumukuha ng closure, at ang closure ay kumukuha ng self. Sa Kotlin/Java, ang pagtagas ay nangyayari kapag ang Activity ay nagpasa ng inner class o lambda sa isang mahabang background operation. Ayon sa WWDC Session 10216 (2024), ang hindi wastong pamamahala ng mga closure ay ang ikatlong pinakakaraniwang sanhi ng pagtagas ng memory sa iOS application.
Ang Swift ay gumagamit ng Automatic Reference Counting (ARC), na nagpapalaya sa object kapag ang reference counter ay na-zero. Capture list [weak self] o [unowned self] sa closure ay pumipigil sa retain cycle. Ang weak self ay lumilikha ng opsyonal na reference na nagiging nil sa deallocation ng object. Ang unowned self ay nag-aakala na ang object ay nabubuhay nang mas matagal kaysa sa closure — ang paglabag sa palagay na ito ay nagdudulot ng crash. Ang paggamit ng weak self bilang ligtas na default na opsyon ay inirerekomenda.
class DataController {
var onDataUpdate: ((String) -> Void)?
func setupCallback() {
// Retain cycle!
onDataUpdate = { text in
self.process(text)
}
// Naayos: [weak self]
onDataUpdate = { [weak self] text in
guard let self else { return }
self.process(text)
}
}
func process(_ input: String) { }
}
Sa Android, ang pagtagas ng callback ay nangyayari kapag ang Activity o Fragment ay nagpasa ng listener sa isang singleton component (hal. EventBus o Service). WeakReference ay nagpapahintulot sa garbage collector na palayain ang Activity, kahit na may mahinang reference dito. Ang mga Lifecycle-aware component (LiveData, Flow) ay awtomatikong nilulutas ang problema. Ang Kotlin lambda na kumukuha ng Activity context ay maaari ring magdulot ng pagtagas: ang lambda ay implicit na nag-iimbak ng reference sa this.
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()
}
}
}
// Paggamit sa Fragment
manager.addListener { result ->
// WeakReference ay hindi pinapanatili ang Fragment
updateUI(result)
}
Callback Hell (kilala rin bilang Pyramid of Doom) — sitwasyon kung saan maraming nested callback ang lumilikha ng malalim na nested code structure na mahirap basahin at i-debug. Ang bawat susunod na hakbang ay nangangailangan ng paghihintay sa pagkumpleto ng nauna, na humahantong sa 5-10 antas ng nesting. Ang problemang ito ay katangian ng sequential asynchronous operations: pag-load ng data → parsing → pag-save sa database → pag-update ng UI.
Ang Swift 5.5 ay nagpakilala ng mga asynchronous function (async/await) na nagpapahintulot sa pagsulat ng asynchronous code nang sequential. AsyncSequence at AsyncStream ay pinapalitan ang callback-based iterations. Ang Combine framework ay nagbibigay ng mga operator na flatMap, merge, combineLatest para sa komposisyon ng asynchronous streams nang walang nesting. Gayunpaman, ang callback ay nananatiling kinakailangan para sa pagtatrabaho sa Objective-C API at third-party libraries na walang async support.
// Nested callback — 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 — solusyon
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)
}
Ang Kotlin coroutine ay pinapalitan ang callback ng suspend function na may sequential execution. Flow ay nagbibigay ng cold streams na may mga operator na map, flatMapConcat, combine. Ang CoroutineScope ay nagpapahintulot sa pagkansela ng lahat ng inilunsad na coroutine kapag nawasak ang component. Ang Room, Retrofit, at iba pang Jetpack library ay may built-in na suporta para sa suspend function, na nag-aalis ng pangangailangan para sa callback para sa standard operations.
// Sequential callback — 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 — solusyon
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 at Delegate — dalawang approach sa asynchronous notification, at ang pagpili sa pagitan ng mga ito ay depende sa architectural requirements. Ang callback ay angkop para sa isang beses na operasyon na may isang resulta. Ang delegate ay dinisenyo para sa maramihang mga event na may iba't ibang signature ng method. Inirerekomenda ng Apple ang delegate para sa kumplikadong protocol na may maraming method, callback — para sa simpleng closure na may isang resulta. Sa Android, pinapalitan ng callback ang delegate sa karamihan ng mga kaso dahil sa suporta ng lambda.
Ang callback ay optimal para sa mga operasyon na may iisang resulta: network request, pagbabasa ng file, animation na may completion block. Mga Bentahe: kompaktong syntax, walang hiwalay na protocol, direktang pagkuha ng konteksto. Mga Disadvantages: komplikasyon sa maraming resulta (progreso, pause, pagkansela), imposibilidad ng maramihang pagpapadala (kung ang callback ay maaaring tawagin nang higit sa isang beses — gumamit ng publisher).
Ang delegate ay angkop para sa mga protocol na may maraming mandatory at optional method: UITableViewDelegate, CLLocationManagerDelegate, Bluetooth connection. Mga Bentahe: malinaw na tipifikasi ng bawat method, dokumentasyon sa pamamagitan ng protocol, suporta para sa optional method sa pamamagitan ng @objc optional. Mga Disadvantages: boilerplate code, mandatoryong mahinang reference sa delegate (weak var delegate), komplikasyon sa pagkuha ng konteksto.
Mga Madalas Itanong
Callback ay isang espesyal na kaso ng higher-order function. Ang higher-order function ay tumatanggap ng ibang function bilang argumento o ibinabalik ito. Ang callback ay isang function na espesyal na ipinapasa para sa asynchronous execution pagkatapos makumpleto ang operasyon. Lahat ng callback ay ipinapatupad sa pamamagitan ng higher-order function, ngunit hindi lahat ng higher-order function ay callback.
Ayon sa convention, ang callback ay dapat tawagin nang eksaktong isang beses — alinman sa success o failure. Maramihang pagtawag ng parehong callback ay itinuturing na error sa disenyo. Para sa maramihang mga event (progreso, daloy ng data) gumamit ng Observable, Publisher o Flow — sinusuportahan nila ang maramihang paglabas ng halaga. Ang ilang API ay lumalabag sa panuntunang ito, na humahantong sa mahirap matukoy na bug.
Trailing closure — syntactic sugar ng Swift na nagpapahintulot sa pagpasa ng closure pagkatapos ng mga round bracket ng function call. Kung ang function ay tumatanggap ng closure bilang huling argumento, maaari itong ilagay sa labas ng bracket: fetchData { result in ... }. Para sa maramihang closure, ang trailing closure ay inilalapat lamang sa huli, ang iba ay pinangalanan sa loob ng bracket. Pinapabuti nito ang pagiging nababasa ng callback-based na API.
Gumamit ng WeakReference para sa mahabang buhay na listener, kanselahin ang coroutine sa pamamagitan ng Job.cancel() sa onDestroy(), ilapat ang lifecycleScope para sa awtomatikong pagkansela. Ang ViewModel + LiveData/Flow ay nilulutas ang problema sa antas ng arkitektura. Iwasan ang pagpasa ng Activity context sa static callback — gumamit ng Application context. Ang Kotlin lambda ay implicit na kumukuha ng this, suriin sa pamamagitan ng memory profiler.
Async/await ay pinapalitan ang callback para sa sequential asynchronous code, ngunit hindi para sa event-driven architecture. Ang callback ay nananatili sa system API (View.OnClickListener, URLSession delegate), callback na may progreso, at third-party library. Ang buong pagpapalit ay imposible dahil sa backward compatibility. Ang modernong diskarte ay ang paggamit ng async/await na may callback wrapper (continuation sa Swift, suspendCancellableCoroutine sa Kotlin).
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din