Callback — qu'est-ce que c'est, fonctions de rappel et comment elles fonctionnent

Auteur : IT Sectr Publié le : 2026-03-17 Temps de lecture : 11 min

Callback est une fonction qui est passée à une autre fonction en tant qu'argument et exécutée après la fin d'une opération asynchrone. Dans le développement mobile, le callback est utilisé pour traiter les résultats de requêtes réseau, les opérations de base de données et les animations. Selon la Documentation Apple (2025), les closures en Swift sont la forme principale de callback et sont utilisées dans URLSession, GCD et Combine. Dans Android, le callback est implémenté via des interfaces, des lambdas Kotlin et ListenableFuture.

Points Clés

  • Callback — fonction de rappel passée comme argument pour une exécution asynchrone.
  • Swift utilise des closures avec le mot-clé @escaping pour les callbacks.
  • Kotlin utilise des expressions lambda et des fonctions d'ordre supérieur pour les callbacks.
  • Retain cycle — fuite mémoire lors de la capture de self dans un callback sur iOS.
  • Callback Hell — problème de callbacks imbriqués, résolu par async/await et les coroutines.

Qu'est-ce qu'un Callback ?

Callback (fonction de rappel) est un code exécutable qui est passé à une autre fonction et appelé après l'achèvement d'une action spécifique. Dans le développement mobile, le callback est un mécanisme fondamental de la programmation asynchrone, permettant de réagir à l'achèvement de requêtes réseau, de temporisateurs, d'animations et d'opérations d'E/S sans bloquer le thread principal. Swift et Kotlin fournissent des constructions syntaxiques intégrées pour créer des callbacks — des closures et des lambdas respectivement.

Principe de fonctionnement du Callback

Une fonction d'ordre supérieur accepte une autre fonction comme paramètre et l'appelle après avoir exécuté sa logique principale. Le flux de contrôle est renvoyé à l'appelant via le callback, d'où le nom. Dans iOS, le callback est utilisé dans UIKit (animations UIView.animate), Foundation (URLSession.dataTask) et Combine (sink). Dans Android, le callback est utilisé dans View.OnClickListener, Retrofit Callback et Room DAO. Les API modernes remplacent de plus en plus les callbacks par async/await ou des coroutines, mais comprendre les callbacks est nécessaire pour travailler avec du code legacy et des API de bas niveau.

Callback synchrone et asynchrone

Le callback peut être synchrone (appelé immédiatement dans la fonction) et asynchrone (appelé plus tard depuis un autre thread ou une autre file d'attente). Les callbacks synchrones sont utilisés pour le tri (comparateurs) et le parcours de collections. Les callbacks asynchrones sont utilisés pour les requêtes réseau, la lecture de fichiers et le travail avec les capteurs. La différence est cruciale pour comprendre le threading : le callback synchrone s'exécute dans le même thread, le callback asynchrone dans un thread déterminé par le dispatcher (DispatchQueue dans iOS, Dispatchers dans Kotlin).

Comment fonctionne le Callback dans iOS et Android ?

Le mécanisme de callback sur les deux plateformes est basé sur le même principe : une fonction est passée comme objet de première classe et stockée jusqu'au moment de l'exécution. Cependant, les implémentations diffèrent en raison de paradigmes linguistiques différents. Dans iOS, un callback est une closure qui capture des variables du contexte environnant. Dans Android, un callback est le plus souvent implémenté via des classes anonymes ou des expressions lambda Kotlin, compilées en FunctionalInterface.

Cycle de vie du Callback dans iOS

Lors de l'appel d'une fonction asynchrone, la closure est stockée sur le tas avec les variables capturées. Lorsque l'opération se termine, le système GCD ou OperationQueue place le callback dans la file d'attente appropriée (file principale ou file d'arrière-plan). Après l'exécution, le callback est supprimé de la mémoire lorsqu'il n'y a plus de références fortes. La capture list ([weak self]) empêche la rétention de l'objet après sa désallocation. Sans capture list, un retain cycle se produit, où l'objet et le callback se référencent mutuellement.

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

// Utilisation avec [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)
    }
}

Cycle de vie du Callback dans Android

Dans Android, le callback est passé via une interface ou une lambda. Lors de l'exécution d'une opération asynchrone via ExecutorService ou une coroutine, le callback est stocké en mémoire jusqu'à la fin du travail en arrière-plan. Les lambdas Kotlin sont compilées en classes anonymes qui capturent des variables externes. L'absence de références faibles dans la JVM nécessite une gestion manuelle : annulation du callback dans onDestroy() ou annulation des coroutines via Job.cancel(). ViewModel et LiveData résolvent ce problème au niveau du composant architectural.

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

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

Syntaxe du Callback en Swift et Kotlin

La syntaxe du callback est déterminée par la capacité du langage à travailler avec les fonctions comme objets de première classe. En Swift, les closures ont une syntaxe concise avec des noms d'arguments automatiques ($0, $1). En Kotlin, les lambdas supportent également it pour un seul argument. Les différences apparaissent dans le traitement de la capture de variables (capture list en Swift vs références mutables en Kotlin) et le typage (Result vs Result).

Callback en Swift : closures

Une closure Swift est un bloc de code autonome qui peut être passé et utilisé dans une autre fonction. Les closures peuvent être globales (nommées), imbriquées et au niveau expression. @escaping marque une closure qui sera exécutée après le retour de la fonction — c'est une exigence obligatoire pour les callbacks asynchrones. Sans @escaping, la closure ne peut être exécutée qu'à l'intérieur du corps de la fonction. La syntaxe trailing closure permet de passer la closure après les parenthèses : 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 en Kotlin : lambdas et fonctions d'ordre supérieur

Kotlin supporte les fonctions d'ordre supérieur qui acceptent d'autres fonctions comme paramètres. Le callback en Kotlin est passé via un paramètre de type (T) -> Unit ou (T) -> R pour les valeurs de retour. Les fonctions suspend des coroutines Kotlin remplacent les callbacks par du code séquentiel, mais les callbacks restent dans les API compatibles Java et le SDK Android (View.setOnClickListener, TextWatcher). Les lambdas Kotlin capturent automatiquement les variables val, les variables var nécessitent des wrappers de mutabilité.

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

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

Retain cycles et fuites mémoire dans le Callback

Retain cycle est une situation où deux objets maintiennent des références fortes l'un sur l'autre, empêchant le gestionnaire de mémoire de les libérer. En Swift, le retain cycle se produit lorsqu'un viewController capture une closure et que la closure capture self. En Kotlin/Java, la fuite se produit lorsqu'une Activity passe une classe interne ou une lambda à une opération d'arrière-plan de longue durée. Selon la Session WWDC 10216 (2024), la gestion incorrecte des closures est la troisième cause la plus fréquente de fuites mémoire dans les applications iOS.

Retain cycles en Swift

Swift utilise le Comptage Automatique de Références (ARC), qui libère un objet lorsque le compteur de références atteint zéro. La capture list [weak self] ou [unowned self] dans une closure empêche les retain cycles. weak self crée une référence optionnelle qui devient nil lors de la désallocation de l'objet. unowned self suppose que l'objet vit plus longtemps que la closure — la violation de cette hypothèse provoque un crash. L'utilisation de weak self comme option sécurisée par défaut est recommandée.

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

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

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

    func process(_ input: String) { }
}

Fuites mémoire dans Android

Dans Android, la fuite mémoire par callback se produit lorsqu'une Activity ou un Fragment passe un écouteur à un composant singleton (par exemple, EventBus ou Service). WeakReference permet au ramasse-miettes de libérer l'Activity même s'il existe une référence faible vers elle. Les composants lifecycle-aware (LiveData, Flow) résolvent le problème automatiquement. Les lambdas Kotlin qui capturent le contexte de l'Activity peuvent également causer des fuites : la lambda stocke implicitement une référence à 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()
        }
    }
}

// Utilisation dans Fragment
manager.addListener { result ->
    // WeakReference ne retient pas Fragment
    updateUI(result)
}

Callback Hell et moyens de le combattre

Callback Hell (également connu sous le nom de Pyramide de la Perdition) est une situation où de multiples callbacks imbriqués créent une structure de code profondément imbriquée, difficile à lire et à déboguer. Chaque étape suivante nécessite d'attendre la fin de la précédente, ce qui entraîne 5 à 10 niveaux d'imbrication. Ce problème est caractéristique des opérations asynchrones séquentielles : charger des données → analyser → sauvegarder dans la BD → mettre à jour l'UI.

Solutions en Swift : async/await

Swift 5.5 a introduit les fonctions asynchrones (async/await), qui permettent d'écrire du code asynchrone de manière séquentielle. AsyncSequence et AsyncStream remplacent les itérations basées sur les callbacks. Le framework Combine fournit des opérateurs flatMap, merge, combineLatest pour la composition de flux asynchrones sans imbrication. Cependant, les callbacks restent nécessaires pour travailler avec les API Objective-C et les bibliothèques tierces sans support async.

swift
// Callbacks imbriqués — 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 — solution
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)
}

Solutions en Kotlin : coroutines et Flow

Les coroutines Kotlin remplacent les callbacks par des fonctions suspend pour une exécution séquentielle. Flow fournit des flux cold avec des opérateurs map, flatMapConcat, combine. CoroutineScope permet d'annuler toutes les coroutines en cours lors de la destruction d'un composant. Room, Retrofit et d'autres bibliothèques Jetpack ont un support intégré pour les fonctions suspend, éliminant le besoin de callbacks dans les opérations standard.

kotlin
// Callbacks séquentiels — Callback Hell
api.login(credentials) { user ->
    api.fetchProfile(user.id) { profile ->
        api.download(profile.avatarUrl) { bytes ->
            file.save(bytes) { result ->
                textView.text = result.toString()
            }
        }
    }
}

// Coroutines — solution
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 Délégué : que choisir ?

Callback et Délégué sont deux approches de la notification asynchrone, et le choix dépend des exigences architecturales. Le callback est adapté aux opérations uniques avec un seul résultat. Le délégué est conçu pour des événements multiples avec différentes signatures de méthodes. Apple recommande le délégué pour les protocoles complexes avec plusieurs méthodes, et le callback pour les closures simples avec un seul résultat. Dans Android, le callback remplace le délégué dans la plupart des cas en raison du support des lambdas.

Quand choisir le Callback

Le callback est optimal pour les opérations avec un seul résultat : requête réseau, lecture de fichier, animation avec bloc de completion. Avantages : syntaxe compacte, absence de protocole séparé, capture directe du contexte. Inconvénients : complexité avec des résultats multiples (progression, pause, annulation), impossibilité d'envoi multiple (si un callback peut être appelé plus d'une fois — utilisez un publisher).

Quand choisir le Délégué

Le délégué est adapté aux protocoles avec plusieurs méthodes obligatoires et optionnelles : UITableViewDelegate, CLLocationManagerDelegate, connexions Bluetooth. Avantages : typage clair de chaque méthode, documentation via le protocole, support des méthodes optionnelles via @objc optional. Inconvénients : code boilerplate, référence faible au délégué obligatoire (weak var delegate), complexité lors de la capture du contexte.

Questions Fréquentes

Quelle est la différence entre un callback et une fonction d'ordre supérieur ?

Callback est un cas particulier de fonction d'ordre supérieur. Une fonction d'ordre supérieur accepte une autre fonction comme argument ou la retourne. Un callback est une fonction passée spécifiquement pour une exécution asynchrone après l'achèvement d'une opération. Tous les callbacks sont implémentés via des fonctions d'ordre supérieur, mais toute fonction d'ordre supérieur n'est pas un callback.

Un callback peut-il être appelé plusieurs fois ?

Par convention, un callback doit être appelé exactement une fois — soit success, soit failure. L'appel multiple d'un même callback est considéré comme une erreur de conception. Pour des événements multiples (progression, flux de données), utilisez Observable, Publisher ou Flow — ils supportent l'émission multiple de valeurs. Certaines API violent cette règle, entraînant des bugs difficiles à trouver.

Qu'est-ce que le trailing closure en Swift ?

Trailing closure est un sucre syntaxique de Swift qui permet de passer une closure après les parenthèses d'appel de fonction. Si une fonction accepte une closure comme dernier argument, elle peut être placée en dehors des parenthèses : fetchData { result in ... }. Pour plusieurs closures, le trailing closure ne s'applique qu'à la dernière ; les autres sont nommées entre les parenthèses. Cela améliore la lisibilité des API basées sur les callbacks.

Comment éviter les fuites mémoire avec les callbacks dans Android ?

Utilisez WeakReference pour les écouteurs longue durée, annulez les coroutines via Job.cancel() dans onDestroy(), utilisez lifecycleScope pour l'annulation automatique. ViewModel + LiveData/Flow résout le problème au niveau architectural. Évitez de passer le contexte d'Activity aux callbacks statiques — utilisez le contexte Application. Les lambdas Kotlin capturent this implicitement, vérifiez avec un profileur mémoire.

Async/await remplacera-t-il complètement les callbacks ?

Async/await remplace les callbacks pour le code asynchrone séquentiel, mais pas pour l'architecture pilotée par les événements. Les callbacks restent dans les API système (View.OnClickListener, délégués URLSession), les callbacks de progression et les bibliothèques tierces. Le remplacement complet est impossible en raison de la rétrocompatibilité. La stratégie moderne consiste à utiliser async/await avec des wrappers de callback (continuation en Swift, suspendCancellableCoroutine en Kotlin).

Résumé

  • Callback — fonction de rappel passée comme argument pour une exécution asynchrone après l'achèvement d'une opération.
  • Swift implémente les callbacks via des closures avec @escaping, capture list [weak self] et syntaxe trailing closure.
  • Kotlin utilise des lambdas, des fonctions d'ordre supérieur et des fonctions suspend de coroutines pour l'asynchronie.
  • Retain cycle dans iOS est évité par la capture list ; dans Android — par WeakReference et les composants lifecycle-aware.
  • Callback Hell est résolu avec async/await en Swift et les coroutines avec Flow en Kotlin.
  • Délégué est préférable au callback pour les protocoles avec plusieurs méthodes ; callback pour les opérations uniques.
  • Utilisez le callback pour les opérations asynchrones simples, async/await pour les chaînes séquentielles, le délégué pour les événements multiples.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi