Callback — ce este, funcții de revenire și cum funcționează

Autor: IT Sectr Publicat: 2026-03-17 Timp de citire: 11 min

Callback — este o funcție transmisă unei alte funcții ca argument și executată după finalizarea unei operații asincrone. În dezvoltarea mobilă, callback-ul este utilizat pentru procesarea rezultatelor cererilor de rețea, lucrul cu baze de date și animații. Conform Apple Documentation (2025), closure-urile în Swift sunt forma principală de callback și sunt utilizate în URLSession, GCD și Combine. În Android, callback-ul este implementat prin interfețe, lambde Kotlin și ListenableFuture.

Principalele

  • Callback — funcție de revenire transmisă ca argument pentru execuție asincronă.
  • Swift utilizează closure-uri cu cuvântul cheie @escaping pentru callback.
  • Kotlin aplică expresii lambda și funcții de ordin superior pentru callback.
  • Retain cycle — scurgere de memorie la capturarea self în callback pe iOS.
  • Callback Hell — problema callback-urilor imbricate, rezolvată prin async/await și corutine.

Ce este Callback?

Callback (funcție de revenire) — este un cod executabil transmis unei alte funcții și apelat după finalizarea unei acțiuni specifice. În dezvoltarea mobilă, callback-ul este mecanismul fundamental al programării asincrone, permițând reacția la finalizarea cererilor de rețea, timerelor, animațiilor și operațiilor de intrare/ieșire fără blocarea firului principal. Swift și Kotlin oferă construcții sintactice încorporate pentru crearea callback-urilor — closure-uri și, respectiv, lambde.

Principiul de funcționare al Callback-ului

O funcție de ordin superior acceptă o altă funcție ca parametru și o apelează după executarea logicii sale principale. Fluxul de control revine apelantului prin callback, de unde și numele. În iOS, callback-ul se aplică în UIKit (animații UIView.animate), Foundation (URLSession.dataTask) și Combine (sink). În Android, callback-ul este utilizat în View.OnClickListener, Retrofit Callback și Room DAO. API-urile moderne înlocuiesc tot mai mult callback-ul cu async/await sau corutine, dar înțelegerea callback-ului este necesară pentru lucrul cu cod moștenit și API-uri de nivel scăzut.

Callback sincron și asincron

Callback-ul poate fi sincron (apelat imediat în interiorul funcției) și asincron (apelat mai târziu dintr-un alt fir sau coadă). Callback-urile sincrone sunt utilizate pentru sortare (comparatori) și parcurgerea colecțiilor. Callback-urile asincrone sunt aplicate pentru cereri de rețea, citire fișiere și lucru cu senzori. Diferența este critică pentru înțelegerea threading-ului: callback-ul sincron se execută în același fir, cel asincron — în firul determinat de dispecer (DispatchQueue în iOS, Dispatchers în Kotlin).

Cum funcționează Callback în iOS și Android?

Mecanismul callback pe ambele platforme se bazează pe același principiu: funcția este transmisă ca obiect de primă clasă și stocată până la momentul executării. Cu toate acestea, implementările diferă din cauza paradigmelor lingvistice diferite. În iOS, callback-ul este un closure care capturează variabile din contextul înconjurător. În Android, callback-ul este cel mai adesea implementat prin clase anonime sau expresii lambda Kotlin, compilate în FunctionalInterface.

Ciclul de viață al Callback-ului în iOS

La apelarea unei funcții asincrone, closure-ul este salvat în heap împreună cu variabilele capturate. Când operația se finalizează, sistemul GCD sau OperationQueue plasează callback-ul în coada corespunzătoare (main queue sau background queue). După execuție, callback-ul este eliminat din memorie în absența referințelor puternice. Capture list ([weak self]) previne menținerea obiectului după dealocarea acestuia. Fără capture list, apare un retain cycle, în care obiectul și callback-ul se referă reciproc unul la celălalt.

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()
}

// Utilizare cu [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)
    }
}

Ciclul de viață al Callback-ului în Android

În Android, callback-ul este transmis printr-o interfață sau o lambdă. La executarea unei operații asincrone prin ExecutorService sau o corutină, callback-ul este stocat în memorie până la finalizarea lucrului în fundal. Lambdele Kotlin sunt compilate în clase anonime care capturează variabile externe. Absența referințelor slabe în JVM necesită gestionare manuală: resetarea callback-ului în onDestroy() sau anularea corutinelor prin Job.cancel(). ViewModel și LiveData rezolvă această problemă la nivel de componentă arhitecturală.

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) }
            }
        }
    }
}

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

Sintaxa Callback în Swift și Kotlin

Sintaxa callback-ului este determinată de capacitățile lingvistice de lucru cu funcții ca obiecte de primă clasă. În Swift, closure-urile au o sintaxă concisă cu nume automate de argumente ($0, $1). În Kotlin, lambdele acceptă de asemenea it pentru un singur argument. Diferențele se manifestă în gestionarea capturării variabilelor (capture list în Swift vs referințe mutabile în Kotlin) și tipizare (Result<Success, Failure> vs Result<T>).

Callback în Swift: closure-uri

Un closure în Swift este un bloc de cod autosuficient care poate fi transmis și utilizat într-o altă funcție. Closure-urile pot fi globale (denumite), imbricate și expression-level. @escaping marchează un closure care va fi executat după revenirea din funcție — aceasta este o cerință obligatorie pentru callback-urile asincrone. Fără @escaping, closure-ul poate fi executat doar în interiorul corpului funcției. Sintaxa trailing closure permite transmiterea closure-ului după parantezele rotunde: 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 în Kotlin: lambde și funcții de ordin superior

Kotlin acceptă funcții de ordin superior care primesc alte funcții ca parametri. Callback-ul în Kotlin este transmis printr-un parametru de tip (T) -> Unit sau (T) -> R pentru valoarea returnată. Funcțiile suspend ale corutinelor Kotlin înlocuiesc callback-ul cu cod secvențial, dar callback-ul rămâne în API-urile compatibile Java și Android SDK (View.setOnClickListener, TextWatcher). Lambdele Kotlin capturează automat variabilele val, variabilele var necesită învelișuri mutabile.

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

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

Retain cycles și scurgeri de memorie în Callback

Retain cycle — situația în care două obiecte mențin referințe puternice unul asupra celuilalt, împiedicând eliberarea lor de către colectorul de memorie. În Swift, retain cycle apare atunci când viewController capturează un closure, iar closure-ul capturează self. În Kotlin/Java, scurgerea apare atunci când Activity transmite o clasă internă sau o lambdă într-o operație lungă de fundal. Conform WWDC Session 10216 (2024), gestionarea incorectă a closure-urilor este a treia cea mai frecventă cauză de scurgeri de memorie în aplicațiile iOS.

Retain cycles în Swift

Swift utilizează Automatic Reference Counting (ARC), care eliberează obiectul la resetarea contorului de referințe. Capture list [weak self] sau [unowned self] în closure previne retain cycle. weak self creează o referință opțională care devine nil la dealocarea obiectului. unowned self presupune că obiectul trăiește mai mult decât closure-ul — încălcarea acestei presupuneri duce la crash. Se recomandă utilizarea weak self ca opțiune sigură implicită.

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

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

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

    func process(_ input: String) { }
}

Scurgeri de memorie în Android

În Android, scurgerea de callback apare atunci când Activity sau Fragment transmite un ascultător unui component singleton (de exemplu, EventBus sau Service). WeakReference permite colectorului de gunoi să elibereze Activity, chiar dacă există o referință slabă asupra sa. Componentele Lifecycle-aware (LiveData, Flow) rezolvă problema automat. Lambdele Kotlin care capturează contextul Activity pot provoca, de asemenea, scurgeri: lambda păstrează implicit o referință la 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()
        }
    }
}

// Utilizare în Fragment
manager.addListener { result ->
    // WeakReference nu reține Fragment
    updateUI(result)
}

Callback Hell și modalități de combatere

Callback Hell (cunoscut și sub numele de Piramida Morții) — este situația în care numeroase callback-uri imbricate creează o structură de cod profund imbricată, dificil de citit și depanat. Fiecare pas următor necesită așteptarea finalizării celui precedent, ceea ce duce la imbricare de 5-10 niveluri. Această problemă este caracteristică operațiilor asincrone secvențiale: încărcare date → parsare → salvare în bază de date → actualizare UI.

Soluții în Swift: async/await

Swift 5.5 a introdus funcții asincrone (async/await) care permit scrierea codului asincron secvențial. AsyncSequence și AsyncStream înlocuiesc iterațiile bazate pe callback. Framework-ul Combine oferă operatori flatMap, merge, combineLatest pentru compunerea fluxurilor asincrone fără imbricare. Cu toate acestea, callback-ul rămâne necesar pentru lucrul cu API-uri Objective-C și biblioteci terțe fără suport async.

swift
// Callback-uri imbricate — 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 — soluție
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)
}

Soluții în Kotlin: corutine și Flow

Corutinele Kotlin înlocuiesc callback-ul cu funcții suspend cu execuție secvențială. Flow oferă fluxuri cold cu operatorii map, flatMapConcat, combine. CoroutineScope permite anularea tuturor corutinelor lansate la distrugerea componentei. Room, Retrofit și alte biblioteci Jetpack au suport încorporat pentru funcții suspend, eliminând necesitatea callback-ului pentru operații standard.

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

// Corutine — soluție
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: ce să alegi?

Callback și Delegate — două abordări pentru notificarea asincronă, iar alegerea între ele depinde de cerințele arhitecturale. Callback-ul este potrivit pentru operații unice cu un singur rezultat. Delegate-ul este destinat evenimentelor multiple cu semnături diferite de metode. Apple recomandă delegate-ul pentru protocoale complexe cu mai multe metode, callback-ul — pentru closure-uri simple cu un singur rezultat. În Android, callback-ul înlocuiește delegate-ul în majoritatea cazurilor datorită suportului pentru lambde.

Când să alegi Callback

Callback-ul este optim pentru operații cu un singur rezultat: cerere de rețea, citire fișier, animație cu bloc de finalizare. Avantaje: sintaxă compactă, lipsa unui protocol separat, capturarea directă a contextului. Dezavantaje: complexitate la rezultate multiple (progres, pauză, anulare), imposibilitatea trimiterii multiple (dacă callback-ul poate fi apelat de mai multe ori — utilizați un publisher).

Când să alegi Delegate

Delegate-ul este potrivit pentru protocoale cu mai multe metode obligatorii și opționale: UITableViewDelegate, CLLocationManagerDelegate, conexiuni Bluetooth. Avantaje: tipizare clară a fiecărei metode, documentare prin protocol, suport pentru metode opționale prin @objc optional. Dezavantaje: cod boilerplate, referință slabă obligatorie la delegate (weak var delegate), complexitate la capturarea contextului.

Întrebări frecvente

Care este diferența dintre callback și o funcție de ordin superior?

Callback este un caz particular al funcției de ordin superior. O funcție de ordin superior primește o altă funcție ca argument sau o returnează. Callback-ul este o funcție transmisă special pentru execuția asincronă după finalizarea operației. Toate callback-urile sunt implementate prin funcții de ordin superior, dar nu orice funcție de ordin superior este un callback.

Poate fi apelat un callback de mai multe ori?

Prin convenție, callback-ul trebuie apelat exact o dată — fie success, fie failure. Apelarea multiplă a aceluiași callback este considerată o eroare de proiectare. Pentru evenimente multiple (progres, flux de date) utilizați Observable, Publisher sau Flow — acestea acceptă emisia multiplă de valori. Unele API-uri încalcă această regulă, ceea ce duce la bug-uri greu de depistat.

Ce este trailing closure în Swift?

Trailing closure — zahăr sintactic în Swift care permite transmiterea unui closure după parantezele rotunde ale apelului de funcție. Dacă funcția acceptă un closure ca ultim argument, acesta poate fi plasat în afara parantezelor: fetchData { result in ... }. Pentru mai multe closure-uri, trailing closure se aplică doar ultimului, celelalte fiind denumite în interiorul parantezelor. Acest lucru îmbunătățește lizibilitatea API-urilor bazate pe callback.

Cum se evită scurgerea de memorie la callback în Android?

Utilizați WeakReference pentru ascultători de lungă durată, anulați corutinele prin Job.cancel() în onDestroy(), aplicați lifecycleScope pentru anulare automată. ViewModel + LiveData/Flow rezolvă problema la nivel arhitectural. Evitați transmiterea contextului Activity în callback-uri statice — utilizați contextul aplicației. Lambdele Kotlin capturează implicit this, verificați prin memory profiler.

Va înlocui async/await complet callback-ul?

Async/await înlocuiește callback-ul pentru cod asincron secvențial, dar nu pentru arhitectura event-driven. Callback-ul rămâne în API-urile de sistem (View.OnClickListener, delegați URLSession), apeluri cu progres și biblioteci terțe. Înlocuirea completă este imposibilă din cauza compatibilității inverse. Strategia modernă este utilizarea async/await cu învelișuri de callback (continuation în Swift, suspendCancellableCoroutine în Kotlin).

Concluzii

  • Callback — funcție de revenire transmisă ca argument pentru execuție asincronă după finalizarea operației.
  • Swift implementează callback-ul prin closure-uri cu @escaping, capture list [weak self] și sintaxa trailing closure.
  • Kotlin utilizează lambde, funcții de ordin superior și funcții suspend ale corutinelor pentru asincronitate.
  • Retain cycle în iOS este prevenit prin capture list; în Android — prin WeakReference și componente Lifecycle-aware.
  • Callback Hell este rezolvat prin async/await în Swift și corutine cu Flow în Kotlin.
  • Delegate este preferabil callback-ului pentru protocoale cu mai multe metode; callback — pentru operații unice.
  • Utilizați callback pentru operații asincrone simple, async/await pentru lanțuri secvențiale, delegate pentru evenimente multiple.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și