Callback — je funkce, která je předána jiné funkci jako argument a je provedena po dokončení asynchronní operace. V mobilním vývoji se callback používá pro zpracování výsledků síťových požadavků, práci s databázemi a animace. Podle Apple Documentation (2025) jsou closures ve Swift hlavní formou callback a používají se v URLSession, GCD a Combine. V Androidu je callback implementován přes rozhraní, lambda výrazy Kotlin a ListenableFuture.
Hlavní body
Callback (funkce zpětného volání) — je spustitelný kód, který je předán jiné funkci a je volán po dokončení určité akce. V mobilním vývoji je callback základním mechanismem asynchronního programování, umožňujícím reagovat na dokončení síťových požadavků, časovačů, animací a vstupně-výstupních operací bez blokování hlavního vlákna. Swift a Kotlin poskytují vestavěné syntaktické konstrukce pro vytváření callback — closures a lambdy.
Funkce vyššího řádu přijímá jinou funkci jako parametr a volá ji po provedení své hlavní logiky. Tok řízení se vrací volajícímu přes callback, odtud název. V iOS se callback používá v UIKit (animace UIView.animate), Foundation (URLSession.dataTask) a Combine (sink). V Android se callback používá v View.OnClickListener, Retrofit Callback a Room DAO. Moderní API stále častěji nahrazují callback async/await nebo korutinami, ale porozumění callback je nezbytné pro práci s legacy kódem a nízkoúrovňovými API.
Callback může být synchronní (volán okamžitě uvnitř funkce) a asynchronní (volán později z jiného vlákna nebo fronty). Synchronní callback se používají pro řazení (komparátory) a procházení kolekcí. Asynchronní callback se aplikují pro síťové požadavky, čtení souborů a práci se senzory. Rozdíl je kritický pro pochopení threading: synchronní callback se provádí ve stejném vlákně, asynchronní — ve vlákně určeném dispečerem (DispatchQueue v iOS, Dispatchers v Kotlin).
Mechanismus callback na obou platformách je založen na stejném principu: funkce je předána jako objekt první třídy a uložena až do okamžiku provedení. Implementace se však liší kvůli různým jazykovým paradigmům. V iOS je callback closure, které zachycuje proměnné z okolního kontextu. V Android je callback nejčastěji implementován přes anonymní třídy nebo lambda výrazy Kotlin, kompilované do FunctionalInterface.
Při volání asynchronní funkce je closure uloženo na haldě spolu se zachycenými proměnnými. Když je operace dokončena, systém GCD nebo OperationQueue umístí callback do příslušné fronty (main queue nebo background queue). Po provedení je callback odstraněn z paměti při absenci silných referencí. Capture list ([weak self]) zabraňuje udržení objektu po jeho dealokaci. Bez capture list vzniká retain cycle, kde objekt a callback na sebe vzájemně odkazují.
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()
}
// Použití s [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)
}
}
V Androidu je callback předán přes rozhraní nebo lambdu. Při provádění asynchronní operace přes ExecutorService nebo korutinu je callback uložen v paměti až do dokončení práce na pozadí. Kotlin lambdy jsou kompilovány do anonymních tříd, které zachycují vnější proměnné. Absence slabých referencí v JVM vyžaduje ruční správu: vynulování callback v onDestroy() nebo zrušení korutin přes Job.cancel(). ViewModel a LiveData řeší tento problém na úrovni architektonické komponenty.
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) }
}
}
}
}
// Použití s lambdou
repository.loadData(object : Callback<List<User>> {
override fun onSuccess(data: List<User>) { showUsers(data) }
override fun onError(error: Throwable) { showError(error.message) }
})
Syntaxe callback je určena jazykovými možnostmi práce s funkcemi jako objekty první třídy. Ve Swift mají closures stručnou syntaxi s automatickými názvy argumentů ($0, $1). V Kotlin lambdy také podporují it pro jediný argument. Rozdíly se projevují ve zpracování zachycení proměnných (capture list ve Swift vs měnitelné reference v Kotlin) a typizaci (Result<Success, Failure> vs Result<T>).
Swift closure je soběstačný blok kódu, který může být předán a použit v jiné funkci. Closures mohou být globální (pojmenované), vnořené a expression-level. @escaping označuje closure, které bude provedeno po návratu z funkce — to je povinný požadavek pro asynchronní callback. Bez @escaping může být closure provedeno pouze uvnitř těla funkce. Trailing closure syntax umožňuje předat closure za kulaté závorky: 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)
}
}
Kotlin podporuje funkce vyššího řádu, které přijímají jiné funkce jako parametry. Callback v Kotlin je předán přes parametr typu (T) -> Unit nebo (T) -> R pro návratovou hodnotu. Suspend funkce Kotlin korutin nahrazují callback sekvenčním kódem, ale callback zůstává v Java-kompatibilních API a Android SDK (View.setOnClickListener, TextWatcher). Kotlin lambdy automaticky zachycují val proměnné, var proměnné vyžadují měnitelné obaly.
fun <T, R> processWithCallback(
input: T,
transform: (T) -> R,
onResult: (R) -> Unit
) {
thread {
val result = transform(input)
runOnUiThread { onResult(result) }
}
}
// Příklad s lambdou
processWithCallback(
input = "Hello",
transform = { it.length },
onResult = { length ->
textView.text = "Length: $length"
}
)
Retain cycle — situace, kdy dva objekty drží silné reference na sebe navzájem, čímž brání jejich uvolnění garbage collectorem. Ve Swift retain cycle vzniká, když viewController zachytí closure a closure zachytí self. V Kotlin/Java dochází k úniku, když Activity předá vnitřní třídu nebo lambdu dlouhotrvající operaci na pozadí. Podle WWDC Session 10216 (2024) je nesprávné řízení closures třetí nejčastější příčinou úniků paměti v iOS aplikacích.
Swift používá Automatic Reference Counting (ARC), který uvolní objekt při vynulování počítadla referencí. Capture list [weak self] nebo [unowned self] v closures zabraňuje retain cycle. weak self vytváří volitelnou referenci, která se stane nil při dealokaci objektu. unowned self předpokládá, že objekt žije déle než closure — porušení tohoto předpokladu způsobí crash. Použití weak self jako bezpečné výchozí možnosti se doporučuje.
class DataController {
var onDataUpdate: ((String) -> Void)?
func setupCallback() {
// Retain cycle!
onDataUpdate = { text in
self.process(text)
}
// Opraveno: [weak self]
onDataUpdate = { [weak self] text in
guard let self else { return }
self.process(text)
}
}
func process(_ input: String) { }
}
V Androidu dochází k úniku callback, když Activity nebo Fragment předá posluchače komponentě singleton (např. EventBus nebo Service). WeakReference umožňuje garbage collectoru uvolnit Activity, i když na ni existuje slabá reference. Lifecycle-aware komponenty (LiveData, Flow) řeší problém automaticky. Kotlin lambdy zachycující kontext Activity mohou také způsobit únik: lambda implicitně uchovává referenci na 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()
}
}
}
// Použití ve Fragmentu
manager.addListener { result ->
// WeakReference neudržuje Fragment
updateUI(result)
}
Callback Hell (také známý jako Pyramida zkázy) — situace, kdy mnoho vnořených callback vytváří hluboce vnořenou strukturu kódu, obtížně čitelnou a laditelnou. Každý další krok vyžaduje čekání na dokončení předchozího, což vede k 5-10 úrovním vnoření. Tento problém je charakteristický pro sekvenční asynchronní operace: načtení dat → parsování → uložení do databáze → aktualizace UI.
Swift 5.5 zavedl asynchronní funkce (async/await), které umožňují psát asynchronní kód sekvenčně. AsyncSequence a AsyncStream nahrazují iterace založené na callback. Framework Combine poskytuje operátory flatMap, merge, combineLatest pro kompozici asynchronních toků bez vnoření. Callback však zůstává nezbytný pro práci s Objective-C API a knihovnami třetích stran bez podpory async.
// Vnořené 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 — řešení
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)
}
Kotlin korutiny nahrazují callback suspend funkcemi se sekvenčním prováděním. Flow poskytuje cold toky s operátory map, flatMapConcat, combine. CoroutineScope umožňuje zrušit všechny spuštěné korutiny při zničení komponenty. Room, Retrofit a další Jetpack knihovny mají vestavěnou podporu pro suspend funkce, čímž eliminují potřebu callback pro standardní operace.
// Sekvenční 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()
}
}
}
}
// Korutiny — řešení
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 a Delegate — dva přístupy k asynchronnímu oznámení a výběr mezi nimi závisí na architektonických požadavcích. Callback je vhodný pro jednorázové operace s jedním výsledkem. Delegate je určen pro vícenásobné události s různými podpisy metod. Apple doporučuje delegate pro složité protokoly s několika metodami, callback — pro jednoduchá closures s jedním výsledkem. V Androidu callback nahrazuje delegate ve většině případů kvůli podpoře lambd.
Callback je optimální pro operace s jediným výsledkem: síťový požadavek, čtení souboru, animace s completion blokem. Výhody: kompaktní syntaxe, žádný samostatný protokol, přímé zachycení kontextu. Nevýhody: složitost při vícenásobných výsledcích (průběh, pauza, zrušení), nemožnost vícenásobného odeslání (pokud může být callback volán více než jednou — použijte publisher).
Delegate je vhodný pro protokoly s několika povinnými a volitelnými metodami: UITableViewDelegate, CLLocationManagerDelegate, Bluetooth připojení. Výhody: jasná typizace každé metody, dokumentace přes protokol, podpora volitelných metod přes @objc optional. Nevýhody: boilerplate kód, povinná slabá reference na delegate (weak var delegate), složitost při zachycení kontextu.
Často kladené otázky
Callback je zvláštní případ funkce vyššího řádu. Funkce vyššího řádu přijímá jinou funkci jako argument nebo ji vrací. Callback je funkce předaná speciálně pro asynchronní provedení po dokončení operace. Všechny callback jsou implementovány přes funkce vyššího řádu, ale ne každá funkce vyššího řádu je callback.
Podle konvence by callback měl být volán přesně jednou — buď success nebo failure. Vícenásobné volání stejného callback je považováno za chybu návrhu. Pro vícenásobné události (průběh, datový tok) použijte Observable, Publisher nebo Flow — podporují vícenásobnou emisi hodnot. Některá API toto pravidlo porušují, což vede k obtížně odhalitelným chybám.
Trailing closure — syntaktický cukr Swift, který umožňuje předat closure za kulaté závorky volání funkce. Pokud funkce přijímá closure jako poslední argument, lze jej umístit mimo závorky: fetchData { result in ... }. Pro několik closures se trailing closure aplikuje pouze na poslední, ostatní se pojmenují uvnitř závorek. To zlepšuje čitelnost API založených na callback.
Použijte WeakReference pro dlouho žijící posluchače, rušte korutiny přes Job.cancel() v onDestroy(), aplikujte lifecycleScope pro automatické zrušení. ViewModel + LiveData/Flow řeší problém na architektonické úrovni. Vyhněte se předávání kontextu Activity do statických callback — použijte kontext aplikace. Kotlin lambdy implicitně zachycují this, kontrolujte pomocí memory profileru.
Async/await nahrazuje callback pro sekvenční asynchronní kód, ale ne pro event-driven architekturu. Callback zůstává v systémových API (View.OnClickListener, delegáti URLSession), zpětných voláních s průběhem a knihovnách třetích stran. Úplné nahrazení je nemožné kvůli zpětné kompatibilitě. Moderní strategií je použití async/await s callback obaly (continuation ve Swift, suspendCancellableCoroutine v Kotlin).
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také