Introduction à la programmation asynchrone en Swift
Les applications mobiles et de bureau modernes doivent être réactives et rapides. Personne n'aime quand l'interface se fige lors du chargement de données depuis le réseau ou du traitement d'un fichier volumineux. C'est là que la programmation asynchrone vient à la rescousse. En Swift, depuis la version 5.5, un ensemble d'outils puissant et intuitif pour travailler avec l'asynchrone est apparu — async/await, Task et Actor.
Dans ce guide, nous allons aborder les bases de la programmation asynchrone en Swift pour iOS et macOS. Vous apprendrez à écrire du code asynchrone sans callbacks (callback hell), à gérer correctement les threads et à éviter les conditions de course. Ce contenu est destiné aux développeurs débutants qui connaissent déjà les bases de la syntaxe Swift.
Qu'est-ce que la programmation asynchrone et pourquoi en a-t-on besoin ?
Imaginez que votre application est un chef dans une cuisine. Si le chef attend que la bouilloire bout et ne fait rien, le déjeuner sera retardé. La programmation asynchrone permet au « chef » (le thread principal) de mettre la bouilloire et, sans attendre qu'elle bout, de s'occuper de couper la salade. Quand la bouilloire bout, le chef y revient.
Dans le contexte iOS, cela signifie que les opérations lourdes (téléchargement d'image, parsing JSON, travail avec la base de données) s'exécutent en arrière-plan, sans bloquer le thread principal qui est responsable de la mise à jour de l'interface. Si cela n'est pas fait, l'application « se fige » et sera bientôt fermée de force par le système.
Le problème de l'ancienne approche : callbacks et GCD
Avant l'apparition de async/await, les développeurs utilisaient Grand Central Dispatch (GCD) et les closures (callbacks). Cela conduisait à ce qu'on appelle « l'enfer des callbacks » — des blocs de code imbriqués difficiles à lire, déboguer et maintenir.
// Ancien style avec GCD et closuresfunc fetchUserData(completion: @escaping (User?) -> Void) { DispatchQueue.global().async { // Opération longue let user = User(name: "Иван") DispatchQueue.main.async { completion(user) } }}
fetchUserData { user in guard let user = user else { return } fetchUserPosts(userId: user.id) { posts in // Encore un callback imbriqué print(posts) }}Swift moderne propose une solution bien plus élégante.
Les bases de Swift Concurrency : async/await
Déclaration d'une fonction asynchrone
Pour rendre une fonction asynchrone, ajoutez le mot-clé async après la liste des paramètres. Pour appeler une telle fonction, il faut utiliser le mot-clé await.
// Fonction asynchronefunc fetchImage(from url: URL) async throws -> UIImage { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw URLError(.badServerResponse) } return image}
// Appel de la fonctionTask { do { let image = try await fetchImage(from: url) self.imageView.image = image } catch { print("Ошибка загрузки: \\(error)") }}Notez que URLSession.shared.data(from:) est déjà une fonction asynchrone prête à l'emploi d'Apple. Nous l'appelons simplement avec await, et le thread est libéré pour d'autres tâches pendant que les données sont chargées.
Appels séquentiels et parallèles
Vous pouvez appeler des fonctions asynchrones de manière séquentielle ou parallèle.
// Appel séquentiel : la deuxième requête attend la premièrelet user = try await fetchUser(id: 1)let posts = try await fetchPosts(for: user.id)
// Appel parallèle : les deux requêtes s'exécutent simultanémentasync let user = fetchUser(id: 1)async let posts = fetchPosts(for: 1)let (resultUser, resultPosts) = try await (user, posts)En mode parallèle, les deux requêtes sont lancées simultanément, ce qui