Callback — mi ez, visszahívási függvények és hogyan működnek

Szerző: IT Sectr Megjelenés: 2026-03-17 Olvasási idő: 11 perc

Callback — olyan függvény, amelyet argumentumként adnak át egy másik függvénynek, és az aszinkron művelet befejezése után hajtódik végre. A mobilfejlesztésben a callback a hálózati kérések eredményeinek feldolgozására, adatbázis-műveletekre és animációkra használatos. Az Apple Documentation (2025) szerint a Swiftben a closure-k a callback elsődleges formái, amelyeket URLSession-ben, GCD-ben és Combine-ban használnak. Androidban a callback interfészeken, Kotlin lambda kifejezéseken és ListenableFuture-ön keresztül valósul meg.

Főbb pontok

  • Callback — visszahívási függvény, argumentumként átadva aszinkron végrehajtáshoz.
  • Swift closure-ket használ az @escaping kulcsszóval a callback-hez.
  • Kotlin lambda kifejezéseket és magasabb rendű függvényeket alkalmaz a callback-hez.
  • Retain cycle — memóriaszivárgás a self elfogásakor callback-ben iOS-en.
  • Callback Hell — beágyazott callback-ek problémája, async/await és korutinok által megoldva.

Mi az a Callback?

Callback (visszahívási függvény) — végrehajtható kód, amelyet egy másik függvénynek adnak át, és egy adott művelet befejezése után hívódik meg. A mobilfejlesztésben a callback az aszinkron programozás alapvető mechanizmusa, amely lehetővé teszi a hálózati kérések, időzítők, animációk és be-/kimeneti műveletek befejezésére való reagálást a fő szál blokkolása nélkül. A Swift és a Kotlin beépített szintaktikai konstrukciókat biztosít a callback-ek létrehozásához — closure-ket, illetve lambdákat.

A Callback működési elve

A magasabb rendű függvény egy másik függvényt paraméterként fogad el, és a fő logika végrehajtása után hívja meg. A vezérlés a callback-en keresztül tér vissza a hívóhoz, innen az elnevezés. iOS-ben a callback a UIKit-ben (UIView.animate animációk), Foundation-ben (URLSession.dataTask) és Combine-ban (sink) alkalmazzák. Android-ban a callback a View.OnClickListener-ben, Retrofit Callback-ben és Room DAO-ban használatos. A modern API-k egyre gyakrabban váltják fel a callback-et async/await-re vagy korutinokra, de a callback megértése szükséges az örökölt kód és alacsony szintű API-k kezeléséhez.

Szinkron és aszinkron Callback

A callback lehet szinkron (azonnal meghívódik a függvényen belül) és aszinkron (később hívódik meg egy másik szálból vagy sorból). A szinkron callback-ek rendezésre (komparátorok) és gyűjtemények bejárására használatosak. Aszinkron callback-ek hálózati kérésekre, fájlolvasásra és érzékelőkkel való munkára alkalmazzák. A különbség kritikus a szálkezelés megértéséhez: a szinkron callback ugyanabban a szálban fut, az aszinkron — a diszpécser által meghatározott szálban (DispatchQueue iOS-ben, Dispatchers Kotlin-ban).

Hogyan működik a Callback iOS-ben és Android-ban?

A callback mechanizmus mindkét platformon ugyanazon az elven alapul: a függvény első osztályú objektumként kerül átadásra, és a végrehajtás pillanatáig tárolódik. A megvalósítások azonban eltérnek a különböző nyelvi paradigmák miatt. iOS-ben a callback egy closure, amely változókat fog el a környező kontextusból. Android-ban a callback leggyakrabban névtelen osztályokon vagy Kotlin lambda kifejezéseken keresztül valósul meg, amelyek FunctionalInterface-vé fordulnak.

A Callback életciklusa iOS-ben

Amikor egy aszinkron függvény meghívódik, a closure a heap-en tárolódik az elfogott változókkal együtt. Amikor a művelet befejeződik, a GCD vagy OperationQueue rendszer elhelyezi a callback-et a megfelelő sorban (main queue vagy background queue). A végrehajtás után a callback törlődik a memóriából, ha nincsenek erős referenciák. Capture list ([weak self]) megakadályozza az objektum megtartását a felszabadítás után. Capture list nélkül retain cycle alakul ki, ahol az objektum és a callback kölcsönösen hivatkoznak egymásra.

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

// Használat [weak self]-fel
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)
    }
}

A Callback életciklusa Android-ban

Android-ban a callback interfészen vagy lambdán keresztül adódik át. Amikor egy aszinkron művelet végrehajtódik ExecutorService vagy korutin által, a callback a memóriában tárolódik a háttérmunka befejezéséig. Kotlin lambdák névtelen osztályokká fordulnak, amelyek elfogják a külső változókat. A gyenge referenciák hiánya a JVM-ben kézi kezelést igényel: a callback nullázása onDestroy()-ban vagy a korutinok megszakítása Job.cancel()-lel. A ViewModel és LiveData ezt a problémát architektúrális komponens szinten oldja meg.

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

// Használat lambdával
repository.loadData(object : Callback<List<User>> {
    override fun onSuccess(data: List<User>) { showUsers(data) }
    override fun onError(error: Throwable) { showError(error.message) }
})

Callback szintaxis Swift-ben és Kotlin-ban

A callback szintaxisát a függvények első osztályú objektumként való kezelésének nyelvi képességei határozzák meg. Swift-ben a closure-k rövid szintaxissal rendelkeznek automatikus argumentumnevekkel ($0, $1). Kotlin-ban a lambdák szintén támogatják az it-t egyetlen argumentum esetén. A különbségek a változóelfogás kezelésében (capture list Swift-ben vs változtatható referenciák Kotlin-ban) és a típusosságban (Result<Success, Failure> vs Result<T>) nyilvánulnak meg.

Callback Swift-ben: closure-k

A Swift closure egy önálló kódblokk, amely átadható és használható egy másik függvényben. A closure-k lehetnek globális (nevesített), beágyazott és expression-level. @escaping jelöli azt a closure-t, amely a függvényből való visszatérés után hajtódik végre — ez kötelező követelmény az aszinkron callback-eknél. @escaping nélkül a closure csak a függvény törzsén belül hajtható végre. A trailing closure szintaxis lehetővé teszi a closure átadását a kerek zárójelek után: 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 Kotlin-ban: lambdák és magasabb rendű függvények

A Kotlin támogatja a magasabb rendű függvényeket, amelyek más függvényeket fogadnak el paraméterként. Callback Kotlin-ban (T) -> Unit típusú paraméteren vagy (T) -> R típuson keresztül adódik át a visszatérési értékhez. A Kotlin korutinok suspend függvényei helyettesítik a callback-et szekvenciális kóddal, de a callback megmarad a Java-kompatibilis API-kban és az Android SDK-ban (View.setOnClickListener, TextWatcher). A Kotlin lambdák automatikusan elfogják a val változókat, a var változók változtatható burkolókat igényelnek.

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

// Példa lambdával
processWithCallback(
    input = "Hello",
    transform = { it.length },
    onResult = { length ->
        textView.text = "Length: $length"
    }
)

Retain cycle-ek és memóriaszivárgás a Callback-ben

Retain cycle — olyan helyzet, amikor két objektum erős referenciákat tart fenn egymásra, megakadályozva a memória felszabadítását a garbage collector által. Swift-ben retain cycle akkor keletkezik, amikor a viewController elfog egy closure-t, és a closure elfogja a self-et. Kotlin/Java-ban szivárgás akkor történik, amikor az Activity egy belső osztályt vagy lambdát ad át egy hosszú háttérműveletnek. A WWDC Session 10216 (2024) szerint a closure-k helytelen kezelése a harmadik leggyakoribb memóriaszivárgási ok iOS alkalmazásokban.

Retain cycle-ek Swift-ben

A Swift Automatic Reference Counting (ARC) rendszert használ, amely felszabadítja az objektumot, amikor a referenciaszámláló nullázódik. Capture list [weak self] vagy [unowned self] a closure-ben megakadályozza a retain cycle-t. A weak self opcionális referenciát hoz létre, amely nil lesz az objektum felszabadításakor. Az unowned self feltételezi, hogy az objektum tovább él, mint a closure — ennek megsértése crash-t okoz. A weak self használata biztonságos alapértelmezett opcióként ajánlott.

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

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

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

    func process(_ input: String) { }
}

Memóriaszivárgás Android-ban

Android-ban callback-szivárgás akkor következik be, amikor az Activity vagy Fragment egy listener-t ad át egy singleton komponensnek (pl. EventBus vagy Service). WeakReference lehetővé teszi a garbage collector számára az Activity felszabadítását, még akkor is, ha gyenge referencia mutat rá. Az Lifecycle-aware komponensek (LiveData, Flow) automatikusan megoldják a problémát. A Kotlin lambdák, amelyek Activity kontextust fognak el, szintén okozhatnak szivárgást: a lambda implicit módon tárol egy referenciát a this-re.

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

// Használat Fragment-ben
manager.addListener { result ->
    // WeakReference nem tartja meg a Fragment-et
    updateUI(result)
}

Callback Hell és a leküzdés módjai

Callback Hell (más néven a Végzet Piramisa) — olyan helyzet, amikor számos beágyazott callback mélyen beágyazott kódszerkezetet hoz létre, amely nehezen olvasható és hibakereshető. Minden következő lépés az előző befejezésének megvárását igényli, ami 5-10 szintű beágyazottsághoz vezet. Ez a probléma jellemző a szekvenciális aszinkron műveletekre: adatok betöltése → feldolgozás → mentés adatbázisba → UI frissítése.

Megoldások Swift-ben: async/await

A Swift 5.5 bevezette az aszinkron függvényeket (async/await), amelyek lehetővé teszik az aszinkron kód szekvenciális írását. AsyncSequence és AsyncStream helyettesítik a callback-alapú iterációkat. A Combine keretrendszer flatMap, merge, combineLatest operátorokat biztosít aszinkron folyamok kompozíciójához beágyazás nélkül. A callback azonban továbbra is szükséges az Objective-C API-kkal és async támogatás nélküli külső könyvtárakkal való munkához.

swift
// Beágyazott callback-ek — 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 — megoldás
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)
}

Megoldások Kotlin-ban: korutinok és Flow

A Kotlin korutinok helyettesítik a callback-et suspend függvényekkel szekvenciális végrehajtással. Flow hideg folyamokat biztosít map, flatMapConcat, combine operátorokkal. A CoroutineScope lehetővé teszi az összes elindított korutin megszakítását a komponens megsemmisülésekor. A Room, Retrofit és más Jetpack könyvtárak beépített támogatással rendelkeznek a suspend függvényekhez, kiküszöbölve a callback szükségességét a szabványos műveletekhez.

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

// Korutinok — megoldás
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: mit válasszunk?

Callback és Delegate — két megközelítés az aszinkron értesítéshez, és a köztük való választás az architekturális követelményektől függ. A callback egyszeri műveletekhez alkalmas egyetlen eredménnyel. A delegate több eseményhez készült eltérő metódusaláírásokkal. Az Apple a delegate-t ajánlja összetett, több metódust tartalmazó protokollokhoz, a callback-et — egyszerű, egyetlen eredményt adó closure-khez. Android-ban a callback a legtöbb esetben helyettesíti a delegate-et a lambda-támogatás miatt.

Mikor válasszuk a Callback-et

A callback optimális egyetlen eredménnyel járó műveletekhez: hálózati kérés, fájlolvasás, animáció completion blokkal. Előnyök: kompakt szintaxis, nincs külön protokoll, közvetlen kontextuselfogás. Hátrányok: komplexitás több eredmény esetén (haladás, szünet, megszakítás), többszöri küldés lehetetlensége (ha a callback többször is meghívható — használjon publishtert).

Mikor válasszuk a Delegate-et

A delegate több kötelező és opcionális metódust tartalmazó protokollokhoz alkalmas: UITableViewDelegate, CLLocationManagerDelegate, Bluetooth kapcsolatok. Előnyök: minden metódus világos típusossága, dokumentáció protokollon keresztül, opcionális metódusok támogatása @objc optional segítségével. Hátrányok: boilerplate kód, kötelező gyenge referencia a delegate-hez (weak var delegate), komplexitás a kontextuselfogásban.

Gyakran Ismételt Kérdések

Mi a különbség a callback és a magasabb rendű függvény között?

Callback a magasabb rendű függvény egy speciális esete. A magasabb rendű függvény egy másik függvényt fogad el argumentumként vagy ad vissza. A callback egy olyan függvény, amelyet kifejezetten aszinkron végrehajtásra adnak át a művelet befejezése után. Minden callback magasabb rendű függvényeken keresztül valósul meg, de nem minden magasabb rendű függvény callback.

Meghívható-e a callback többször?

Megállapodás szerint a callback-nek pontosan egyszer kell meghívódnia — vagy success, vagy failure. Többszöri meghívás ugyanazon callback esetében tervezési hibának számít. Több eseményhez (haladás, adatfolyam) használjon Observable-t, Publisher-t vagy Flow-t — ezek támogatják a többszörös értékkibocsátást. Egyes API-k megsértik ezt a szabályt, ami nehezen felderíthető hibákhoz vezet.

Mi az a trailing closure Swift-ben?

Trailing closure — szintaktikai cukor Swift-ben, amely lehetővé teszi a closure átadását a függvényhívás kerek zárójelei után. Ha a függvény a closure-t utolsó argumentumként fogadja el, az a zárójeleken kívül helyezhető: fetchData { result in ... }. Több closure esetén a trailing closure csak az utolsóra alkalmazandó, a többit a zárójeleken belül nevezzük meg. Ez javítja a callback-alapú API-k olvashatóságát.

Hogyan kerüljük el a memóriaszivárgást callback esetén Android-ban?

Használjon WeakReference-t hosszú életű listener-ekhez, szakítsa meg a korutinokat Job.cancel()-lel onDestroy()-ban, alkalmazzon lifecycleScope-ot automatikus megszakításhoz. A ViewModel + LiveData/Flow architektúrális szinten oldja meg a problémát. Kerülje az Activity kontextus átadását statikus callback-ekbe — használjon Application kontextust. A Kotlin lambdák implicit módon elfogják a this-t, ellenőrizze memory profiler-rel.

Teljesen leváltja-e az async/await a callback-et?

Async/await leváltja a callback-et a szekvenciális aszinkron kódhoz, de nem az event-driven architektúrához. A callback megmarad a rendszer API-kban (View.OnClickListener, URLSession delegate-ek), a haladással rendelkező visszahívásokban és külső könyvtárakban. A teljes leváltás lehetetlen a visszamenőleges kompatibilitás miatt. A modern stratégia az async/await használata callback burkolókkal (continuation Swift-ben, suspendCancellableCoroutine Kotlin-ban).

Összefoglalás

  • Callback — visszahívási függvény, argumentumként átadva aszinkron végrehajtáshoz a művelet befejezése után.
  • Swift a callback-et @escaping, capture list [weak self] és trailing closure szintaxisú closure-ökön keresztül valósítja meg.
  • Kotlin lambdákat, magasabb rendű függvényeket és korutinok suspend függvényeit használja az aszinkronitáshoz.
  • Retain cycle iOS-ben capture list segítségével előzhető meg; Android-ban — WeakReference és Lifecycle-aware komponensekkel.
  • Callback Hell Swift-ben async/await, Kotlin-ban Flow-val ellátott korutinok által oldható meg.
  • Delegate előnyösebb a callback-nél több metódust tartalmazó protokollokhoz; callback — egyszeri műveletekhez.
  • Használjon callback-et egyszerű aszinkron műveletekhez, async/await-et szekvenciális láncokhoz, delegate-et több eseményhez.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is