Swift Concurrency : les bases d’Async/Await

Introduction

Async/Await fait partie des nouveautés du framework Concurrency de Swift. Il a été annoncé lors de la WWDC 21 avec Swift 5.5. Il est donc disponible sur iOS 15 mais est rétro-compatible jusqu’à iOS 13.Concurrency est un framework qui permet d’exécuter plusieurs tâches en parallèle. Avec la nouvelle fonction async et la déclaration await, nous pouvons définir des méthodes pour exécuter des tâches asynchrones.

Et avant ça ?

Avant cette nouveauté, on pouvait le faire (avec NSURLSession et des closures) mais c’était plus complexe et on passait souvent par des librairies open-source pour gagner du temps. On avait aussi la possibilité de passer par le framework (natif) Combine mais ce framework est plus destiné à la reactive programmation. Pour de “simples” appels asynchrones c’est plus lourd à mettre en place que Async/Await et on gagne en lisibilité de code !

Lisibilité de code

Utilisation des closures

1    func fetchAllDatas() {
2        /// 1er appel asynchrone
3        fetchFoo { [weak self] result in
4            switch result {
5            case .success(let foo):
6                
7                /// 2ème appel asynchrone
8                self?.fetchBar { [weak self] result in
9                    switch result {
10                    case .success(let bar):
11                        
12                        /// Stockage des réponses
13                        self?.foo = foo + bar
14                        
15                    case .failure(let error):
16                        /// Gestion erreur 2
17                        print("error \(error)")
18                    }
19                }
20            case .failure(let error):
21                /// Gestion erreur 1
22                print("error \(error)")
23            }
24        }
25    }

Utilisation de Async/Await

1    func fetchAwaitDatas() {
2        Task {
3            do {
4                let foo = try await fetchFoo()
5                let bar = try await fetchBar()
6                let fooBar = foo + bar
7            } catch {
8                print("fetch datas failed")
9            }
10        }
11    }

Migration

Il est possible faire une migration d’un code asynchrone en closure vers un système Async/Await avec withCheckedThrowingContinuation ou withCheckedContinuation.

1    func fetchBar() async -> [String] {
2        await withCheckedContinuation { continuation in
3            fetchBar { result in
4                switch result {
5                case .success(let bar):
6                    continuation.resume(returning: bar)
7                   
8                case .failure(let error):
9                    print("error \(error)")
10                    continuation.resume(returning: [])
11                }
12            }
13        }
14    }

Exemples d’utilisation

Si je dois par exemple afficher une liste de données de façon asynchrone en allant chercher ces données depuis deux sources différentes, je peux utiliser await sur chacun de mes appels asynchrones afin de ne pas attendre la réponse du deuxième appel pour commencer à afficher ma liste.

1    @Published var datas: [String] = []
2
3    func fetchAwaitDatas() {
4        Task.init {
5            self.datas = await fetchDatas1()
6            self.datas.append(contentsOf: await fetchDatas2())
7        }
8    }

Exécution des Task

Pour exécuter des tâches asynchrone Async/Await depuis un objet synchrone il faut utiliser une Task. Par défaut la Task est User initiated thread (Apple Developer Documentation ), pour l’executer sur un autre thread on peux utiliser Task.detached(priority: .background).

MainActor

Les Actors permettent d’exécuter du code sur un thread spécifique.
MainActor est disponible et peut être déclaré sur une classe , une extension de classe ou une fonction afin d’exécuter du code sur le main thread (@MainActor).

Si le code asynchrone est exécuté sur un autre thread que le main, il est possible de retourner sur le Main Thread avec le code suivant :

1    await MainActor.run {
2      updateUI()
3    }

Conclusion

Pour conclure pour tout projet Xcode iOS 13+, Async/Await apporte beaucoup d’avantages dont la lisibilité du code et une simplicité de mise en place.
Projet from scratch ? Sans hésitation faites vos appels réseaux avec Async/Await !
Si le projet est existant, une migration progressive pourra être réalisée avec des bénéfices non négligeables pour les maintenances / évolutions futures.

Vous souhaitez en savoir plus ? Contactez-nous !

Antoine Richeux – Développeur iOS
Nathan Porte – Développeur iOS
Manon Bobin – Développeuse iOS