AsyncStream : Maîtriser les flux asynchrones en Swift

Publié le · Mis à jour le · 16 min

Wlad
Wlad
Fondateur & Tech Lead Swift

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 :

AspectAsyncSequenceAsyncStream

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

PolicyCas 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 :

CombineAsyncStream + 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

  1. Intégré au langage : pas de framework externe

  2. Structured concurrency : annulation automatique

  3. Plus simple : pas de AnyCancellable à gérer

  4. Meilleure intégration : fonctionne naturellement avec async/await

Quand garder Combine ?

  1. Opérateurs très spécialisés non disponibles dans Async Algorithms

  2. Code existant fortement basé sur Combine

  3. 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.

Ressources officielles