Introduction à la concurrence et au multithreading en Swift
Les applications modernes pour iOS et macOS doivent être rapides et réactives. L'utilisateur ne doit pas attendre qu'une image se charge ou qu'un calcul complexe s'effectue. Pour cela, Swift propose des mécanismes de concurrence et de multithreading. Dans cet article, nous allons explorer les concepts clés, depuis le classique Grand Central Dispatch (GCD) jusqu'au moderne async/await de Swift Concurrency.
La concurrence (concurrency) est la capacité d'un programme à exécuter plusieurs tâches simultanément en basculant entre elles. Le multithreading (multithreading) est une implémentation de la concurrence où les tâches sont exécutées dans différents threads du système d'exploitation. Swift fournit des outils puissants et sécurisés pour travailler avec ces concepts.
1. Grand Central Dispatch (GCD) — la base du multithreading
Grand Central Dispatch (GCD) est une bibliothèque bas niveau d'Apple pour gérer l'exécution parallèle du code. Elle fonctionne sur la base de files d'attente (dispatch queues).
Types de files d'attente dans GCD
- Serial Queue (file d'attente séquentielle) — les tâches sont exécutées une par une dans l'ordre d'ajout.
- Concurrent Queue (file d'attente parallèle) — les tâches peuvent être exécutées simultanément sur différents threads.
- Main Queue (file d'attente principale) — exécute les tâches sur le thread principal, destinée à la mise à jour de l'UI.
- Global Queues (files d'attente globales) — files d'attente parallèles système avec différentes priorités (QoS).
Exemple : exécution d'une tâche en arrière-plan et mise à jour de l'UI
import UIKit
class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView!
func loadImage(from url: URL) { // Passage à la file d'attente d'arrière-plan DispatchQueue.global(qos: .background).async { [weak self] in guard let data = try? Data(contentsOf: url), let image = UIImage(data: data) else { return } // Retour au thread principal pour la mise à jour de l'UI DispatchQueue.main.async { self?.imageView.image = image } } }}Dans cet exemple, nous téléchargeons l'image dans un thread d'arrière-plan pour ne pas bloquer l'interface, puis nous mettons à jour l'UI sur le thread principal.
DispatchGroup pour la synchronisation des tâches
DispatchGroup permet de suivre l'exécution d'un groupe de tâches asynchrones et d'effectuer une action après leur achèvement.
let group = DispatchGroup()
group.enter()fetchData1 { result in print("Données 1 reçues") group.leave()}
group.enter()fetchData2 { result in print("Données 2 reçues") group.leave()}
group.notify(queue: .main) { print("Toutes les données sont chargées, mise à jour de l'UI")}2. OperationQueue — une approche orientée objet
OperationQueue est une abstraction de plus haut niveau au-dessus de GCD. Elle fonctionne avec des objets Operation, qui représentent une unité de travail.
Avantages d'OperationQueue
- Possibilité d'annuler des tâches (cancel).
- Définition de dépendances entre opérations (addDependency).
- Surveillance de l'état de l'opération (isFinished, isExecuting, isCancelled).
- Configuration du nombre maximum d'opérations parallèles.
Exemple : chargement de plusieurs images
import UIKit
class ImageLoader { private let queue = OperationQueue() func loadImages(from urls: [URL], completion: @escaping ([UIImage]) -> Void) { var images: [UIImage] = [] let completionOperation = BlockOperation { DispatchQueue.main.async { completion(images) } } for url in urls { let o