AsyncStream : Maîtriser les flux asynchrones en Swift
Publié le · Mis à jour le · 16 min
AsyncStream est l'outil idéal pour créer des séquences asynchrones personnalisées qui produisent des valeurs au fil du temps. Que vous bridgiez une API callback, un delegate, ou que vous créiez un flux de données temps réel, AsyncStream simplifie considérablement le travail. Ce guide couvre tout, de la création basique aux patterns avancés avec Swift Async Algorithms.
AsyncSequence vs AsyncStream
Avant de plonger dans AsyncStream, comprenons la différence avec AsyncSequence :
| Aspect | AsyncSequence | AsyncStream |
|---|---|---|
Nature | Protocole | Type concret |
Implémentation | Nécessite un AsyncIterator | Prête à l'emploi |
Cas d'usage | Types personnalisés | Bridging d'APIs existantes |
Complexité | Plus complexe | Plus simple |
AsyncStream est une implémentation concrète d'AsyncSequence. Elle vous évite d'écrire votre propre iterator.
Création simple avec unfolding
La méthode la plus simple pour créer un AsyncStream utilise le closure unfolding :
Cette approche convient quand vous pouvez produire et retourner une valeur directement. Le closure est appelé à chaque fois qu'une nouvelle valeur est attendue.
Limite de l'unfolding
L'unfolding ne fonctionne pas bien avec les APIs callback ou delegate car vous ne contrôlez pas quand les valeurs arrivent. Pour ces cas, utilisez l'approche continuation.
Création avec Continuation
La continuation permet de produire des valeurs depuis n'importe quel contexte :
makeStream : l'API moderne (Swift 5.9+)
Depuis Swift 5.9 (SE-0388), makeStream simplifie la séparation entre producteur et consommateur :
Cette API est backdeployed jusqu'à Swift 5.1, vous pouvez donc l'utiliser même avec des cibles de déploiement anciennes.
Pourquoi préférer makeStream ?
L'ancienne approche avec closure avait un problème : la continuation était disponible uniquement dans le closure de création. Avec makeStream, vous obtenez les deux séparément :
BufferingPolicy : contrôler le buffer
Le buffer détermine comment gérer les valeurs quand le consommateur est plus lent que le producteur :
Choisir la bonne politique
| Policy | Cas d'usage |
|---|---|
.unbounded | Quand chaque valeur compte (logs, analytics) |
.bufferingOldest(n) | Traitement séquentiel obligatoire |
.bufferingNewest(n) | Seule la valeur récente compte (position GPS, prix) |
.bufferingNewest(1) | Toujours la dernière valeur disponible |
.bufferingOldest(0) | Ignorer si pas de consommateur actif |
Exemple pratique : position GPS
AsyncThrowingStream : gérer les erreurs
Quand votre flux peut échouer, utilisez AsyncThrowingStream :
makeStream avec erreurs
Bridging d'APIs existantes
AsyncStream excelle pour moderniser des APIs callback ou delegate :
NotificationCenter
Timer
WebSocket
Swift Async Algorithms
Le package Swift Async Algorithms étend AsyncSequence avec des opérateurs puissants. Ajoutez-le à votre projet :
debounce : attendre une pause
Émet une valeur uniquement après une période de silence :
throttle : limiter la fréquence
Émet au maximum une valeur par intervalle :
merge : combiner plusieurs streams
Fusionne plusieurs streams en un seul :
combineLatest : dernières valeurs de chaque stream
Émet une paire à chaque nouvelle valeur de n'importe quel stream :
zip : associer par position
Attend une valeur de chaque stream avant d'émettre :
chain : enchaîner des séquences
Concatène plusieurs séquences :
Combine vs AsyncStream
Si vous venez de Combine, voici les équivalences :
| Combine | AsyncStream + Async Algorithms |
|---|---|
PassthroughSubject | AsyncStream avec continuation |
CurrentValueSubject | AsyncStream avec .bufferingNewest(1) |
publisher.debounce() | stream.debounce(for:) |
publisher.throttle() | stream.throttle(for:) |
Publishers.Merge | merge() |
Publishers.CombineLatest | combineLatest() |
Publishers.Zip | zip() |
publisher.sink() | for await in |
Avantages d'AsyncStream sur Combine
Intégré au langage : pas de framework externe
Structured concurrency : annulation automatique
Plus simple : pas de AnyCancellable à gérer
Meilleure intégration : fonctionne naturellement avec async/await
Quand garder Combine ?
Opérateurs très spécialisés non disponibles dans Async Algorithms
Code existant fortement basé sur Combine
Intégration SwiftUI avec @Published (bien que @Observable soit préférable)
Annulation et cleanup
La gestion de l'annulation est cruciale pour éviter les fuites de ressources :
Patterns avancés
Retry avec backoff exponentiel
Stream partagé (multicast)
Buffer avec timeout
Bonnes pratiques
1. Toujours appeler finish()
2. Utiliser onTermination pour le cleanup
3. Choisir la bonne BufferingPolicy
4. Gérer la pression arrière (backpressure)
Pièges courants
1. Oublier @Sendable dans onTermination
2. Yield après finish()
3. Stream infini sans condition d'arrêt
Pour aller plus loin
AsyncStream est un outil fondamental pour la programmation réactive en Swift Concurrency. Combiné avec Swift Async Algorithms, il offre une alternative moderne et intégrée à Combine.