Callback é uma função que é passada para outra função como argumento e executada após a conclusão de uma operação assíncrona. No desenvolvimento móvel, o callback é usado para processar resultados de requisições de rede, operações de banco de dados e animações. De acordo com a Documentação da Apple (2025), closures em Swift são a forma principal de callback e são usados em URLSession, GCD e Combine. No Android, o callback é implementado através de interfaces, lambdas Kotlin e ListenableFuture.
Principais Pontos
Callback (função de retorno de chamada) é um código executável que é passado para outra função e chamado após a conclusão de uma ação específica. No desenvolvimento móvel, o callback é um mecanismo fundamental da programação assíncrona, permitindo reagir à conclusão de requisições de rede, temporizadores, animações e operações de E/S sem bloquear a thread principal. Swift e Kotlin fornecem construções sintáticas integradas para criar callbacks — closures e lambdas respectivamente.
Uma função de ordem superior aceita outra função como parâmetro e a chama após executar sua lógica principal. O fluxo de controle é retornado ao chamador através do callback, daí o nome. No iOS, o callback é usado em UIKit (animações UIView.animate), Foundation (URLSession.dataTask) e Combine (sink). No Android, o callback é usado em View.OnClickListener, Retrofit Callback e Room DAO. APIs modernas estão cada vez mais substituindo callbacks por async/await ou corrotinas, mas entender callbacks é necessário para trabalhar com código legado e APIs de baixo nível.
O callback pode ser síncrono (chamado imediatamente dentro da função) e assíncrono (chamado posteriormente de outra thread ou fila). Callbacks síncronos são usados para ordenação (comparadores) e travessia de coleções. Callbacks assíncronos são usados para requisições de rede, leitura de arquivos e trabalho com sensores. A diferença é crítica para entender threading: o callback síncrono executa na mesma thread, o assíncrono executa em uma thread determinada pelo despachante (DispatchQueue no iOS, Dispatchers no Kotlin).
O mecanismo de callback em ambas as plataformas é baseado no mesmo princípio: uma função é passada como objeto de primeira classe e armazenada até o momento da execução. No entanto, as implementações diferem devido a diferentes paradigmas de linguagem. No iOS, um callback é um closure que captura variáveis do contexto circundante. No Android, um callback é mais frequentemente implementado através de classes anônimas ou expressões lambda Kotlin, compiladas em FunctionalInterface.
Ao chamar uma função assíncrona, o closure é armazenado no heap junto com as variáveis capturadas. Quando a operação é concluída, o sistema GCD ou OperationQueue coloca o callback na fila apropriada (fila principal ou fila de fundo). Após a execução, o callback é removido da memória quando não há referências fortes. A capture list ([weak self]) impede que o objeto seja retido após sua desalocação. Sem a capture list, ocorre um retain cycle, onde o objeto e o callback referenciam um ao outro.
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()
}
// Uso com [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)
}
}
No Android, o callback é passado através de uma interface ou lambda. Ao executar uma operação assíncrona via ExecutorService ou uma corrotina, o callback é armazenado em memória até que o trabalho em segundo plano seja concluído. Lambdas Kotlin são compiladas em classes anônimas que capturam variáveis externas. A ausência de referências fracas na JVM requer gerenciamento manual: anular o callback em onDestroy() ou cancelar corrotinas via Job.cancel(). ViewModel e LiveData resolvem esse problema no nível do componente arquitetônico.
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) }
}
}
}
}
// Uso com lambda
repository.loadData(object : Callback<List<User>> {
override fun onSuccess(data: List<User>) { showUsers(data) }
override fun onError(error: Throwable) { showError(error.message) }
})
A sintaxe do callback é determinada pela capacidade da linguagem de trabalhar com funções como objetos de primeira classe. Em Swift, os closures têm uma sintaxe concisa com nomes de argumentos automáticos ($0, $1). Em Kotlin, as lambdas também suportam it para um único argumento. As diferenças se manifestam no tratamento da captura de variáveis (capture list em Swift vs referências mutáveis em Kotlin) e na tipagem (Result
Um closure Swift é um bloco de código autônomo que pode ser passado e usado em outra função. Closures podem ser globais (nomeados), aninhados e de nível de expressão. @escaping marca um closure que será executado após o retorno da função — este é um requisito obrigatório para callbacks assíncronos. Sem @escaping, o closure só pode ser executado dentro do corpo da função. A sintaxe trailing closure permite passar o closure após os parênteses: 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 suporta funções de ordem superior que aceitam outras funções como parâmetros. O callback em Kotlin é passado através de um parâmetro do tipo (T) -> Unit ou (T) -> R para valores de retorno. Funções suspend de corrotinas Kotlin substituem callbacks por código sequencial, mas callbacks permanecem em APIs compatíveis com Java e no SDK Android (View.setOnClickListener, TextWatcher). Lambdas Kotlin capturam automaticamente variáveis val, variáveis var requerem wrappers de mutabilidade.
fun <T, R> processWithCallback(
input: T,
transform: (T) -> R,
onResult: (R) -> Unit
) {
thread {
val result = transform(input)
runOnUiThread { onResult(result) }
}
}
// Exemplo com lambda
processWithCallback(
input = "Hello",
transform = { it.length },
onResult = { length ->
textView.text = "Length: $length"
}
)
Retain cycle é uma situação em que dois objetos mantêm referências fortes um ao outro, impedindo que o gerenciador de memória os libere. Em Swift, o retain cycle ocorre quando um viewController captura um closure e o closure captura self. Em Kotlin/Java, o vazamento ocorre quando uma Activity passa uma classe interna ou lambda para uma operação em segundo plano de longa duração. De acordo com a Sessão 10216 da WWDC (2024), o gerenciamento inadequado de closures é a terceira causa mais comum de vazamentos de memória em aplicativos iOS.
Swift usa Contagem Automática de Referências (ARC), que libera um objeto quando o contador de referências chega a zero. A capture list [weak self] ou [unowned self] em um closure previne retain cycles. weak self cria uma referência opcional que se torna nil quando o objeto é desalocado. unowned self assume que o objeto vive mais que o closure — violar essa suposição causa um crash. Recomenda-se usar weak self como opção segura padrão.
class DataController {
var onDataUpdate: ((String) -> Void)?
func setupCallback() {
// Retain cycle!
onDataUpdate = { text in
self.process(text)
}
// Corrigido: [weak self]
onDataUpdate = { [weak self] text in
guard let self else { return }
self.process(text)
}
}
func process(_ input: String) { }
}
No Android, o vazamento de memória por callback ocorre quando uma Activity ou Fragment passa um listener para um componente singleton (por exemplo, EventBus ou Service). WeakReference permite que o coletor de lixo libere a Activity mesmo se houver uma referência fraca a ela. Componentes lifecycle-aware (LiveData, Flow) resolvem o problema automaticamente. Lambdas Kotlin que capturam o contexto da Activity também podem causar vazamentos: a lambda armazena implicitamente uma referência a 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()
}
}
}
// Uso em Fragment
manager.addListener { result ->
// WeakReference não retém Fragment
updateUI(result)
}
Callback Hell (também conhecido como Pirâmide da Perdição) é uma situação em que múltiplos callbacks aninhados criam uma estrutura de código profundamente aninhada, difícil de ler e depurar. Cada passo seguinte requer esperar a conclusão do anterior, resultando em 5-10 níveis de aninhamento. Este problema é característico de operações assíncronas sequenciais: carregar dados → analisar → salvar no BD → atualizar UI.
Swift 5.5 introduziu funções assíncronas (async/await), que permitem escrever código assíncrono sequencialmente. AsyncSequence e AsyncStream substituem iterações baseadas em callbacks. O framework Combine fornece operadores flatMap, merge, combineLatest para composição de fluxos assíncronos sem aninhamento. No entanto, callbacks permanecem necessários para trabalhar com APIs Objective-C e bibliotecas de terceiros sem suporte async.
// Callbacks aninhados — 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ção
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)
}
Corrotinas Kotlin substituem callbacks por funções suspend para execução sequencial. Flow fornece fluxos cold com operadores map, flatMapConcat, combine. CoroutineScope permite cancelar todas as corrotinas em execução quando um componente é destruído. Room, Retrofit e outras bibliotecas Jetpack têm suporte integrado para funções suspend, eliminando a necessidade de callbacks em operações padrão.
// Callbacks sequenciais — Callback Hell
api.login(credentials) { user ->
api.fetchProfile(user.id) { profile ->
api.download(profile.avatarUrl) { bytes ->
file.save(bytes) { result ->
textView.text = result.toString()
}
}
}
}
// Corrotinas — solução
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 e Delegate são duas abordagens para notificação assíncrona, e a escolha depende dos requisitos arquitetônicos. Callback é adequado para operações únicas com um único resultado. Delegate é projetado para eventos múltiplos com diferentes assinaturas de métodos. A Apple recomenda delegate para protocolos complexos com vários métodos, e callback para closures simples com um único resultado. No Android, o callback substitui o delegate na maioria dos casos devido ao suporte a lambdas.
Callback é ideal para operações com um único resultado: requisição de rede, leitura de arquivo, animação com bloco de conclusão. Vantagens: sintaxe compacta, ausência de protocolo separado, captura direta do contexto. Desvantagens: complexidade com resultados múltiplos (progresso, pausa, cancelamento), impossibilidade de envio múltiplo (se um callback pode ser chamado mais de uma vez — use um publisher).
Delegate é adequado para protocolos com vários métodos obrigatórios e opcionais: UITableViewDelegate, CLLocationManagerDelegate, conexões Bluetooth. Vantagens: tipagem clara de cada método, documentação através do protocolo, suporte a métodos opcionais via @objc optional. Desvantagens: código boilerplate, referência fraca ao delegate obrigatória (weak var delegate), complexidade ao capturar o contexto.
Perguntas Frequentes
Callback é um caso particular de função de ordem superior. Uma função de ordem superior aceita outra função como argumento ou a retorna. Um callback é uma função passada especificamente para execução assíncrona após a conclusão de uma operação. Todos os callbacks são implementados através de funções de ordem superior, mas nem toda função de ordem superior é um callback.
Por convenção, um callback deve ser chamado exatamente uma vez — seja success ou failure. A chamada múltipla do mesmo callback é considerada um erro de design. Para eventos múltiplos (progresso, fluxo de dados), use Observable, Publisher ou Flow — eles suportam emissão múltipla de valores. Algumas APIs violam esta regra, levando a bugs difíceis de encontrar.
Trailing closure é açúcar sintático do Swift que permite passar um closure após os parênteses da chamada da função. Se uma função aceita um closure como último argumento, ele pode ser colocado fora dos parênteses: fetchData { result in ... }. Para múltiplos closures, trailing closure aplica-se apenas ao último; os restantes são nomeados dentro dos parênteses. Isso melhora a legibilidade de APIs baseadas em callbacks.
Use WeakReference para listeners de longa duração, cancele corrotinas via Job.cancel() em onDestroy(), use lifecycleScope para cancelamento automático. ViewModel + LiveData/Flow resolve o problema no nível arquitetônico. Evite passar contexto de Activity para callbacks estáticos — use Application context. Lambdas Kotlin capturam this implicitamente, verifique com um memory profiler.
Async/await substitui callbacks para código assíncrono sequencial, mas não para arquitetura orientada a eventos. Callbacks permanecem em APIs do sistema (View.OnClickListener, delegados URLSession), callbacks de progresso e bibliotecas de terceiros. A substituição completa é impossível devido à compatibilidade reversa. A estratégia moderna é usar async/await com wrappers de callback (continuation em Swift, suspendCancellableCoroutine em Kotlin).
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também