Actor est un type introduit dans Swift 5.5+ qui résout le problème des conditions de course au niveau du langage. Contrairement aux verrous manuels et aux files d'attente DispatchQueue, un actor isole automatiquement son état et synchronise l'accès à celui-ci. Cela signifie que deux threads ne peuvent pas modifier simultanément la même propriété d'un type actor, éliminant ainsi les conditions de course sans effort supplémentaire de la part du développeur. Le mécanisme est basé sur le concept d'isolation d'actor, où le compilateur suit l'accès aux propriétés et méthodes de l'actor. Selon Apple, 2025, le modèle d'actor est une partie fondamentale du système de concurrence de Swift.
Points Clés
Actor est un type de référence, similaire à une classe, mais avec une protection automatique contre les conditions de course. Il a été introduit dans Swift 5.5 dans le cadre du système de concurrence avec async/await et Task. Un actor garantit que son état mutable ne sera jamais lu ou écrit simultanément à partir de différents threads sans synchronisation explicite.
Pour déclarer un actor, on utilise le mot-clé actor suivi d'accolades avec ses membres. Syntaxiquement, un actor ressemble à une classe, mais son comportement diffère radicalement.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
Le compilateur isole automatiquement toutes les propriétés et méthodes de l'actor afin qu'elles ne soient accessibles qu'à l'intérieur du contexte d'actor. En essayant d'accéder à balance depuis l'extérieur de l'actor, le compilateur générera une erreur si l'appel n'est pas marqué comme async.
L'isolation des données est le concept clé d'un actor. Un actor garantit un accès mutuellement exclusif à son état via le mécanisme de l'exécuteur d'actor. Chaque actor a son propre exécuteur qui traite séquentiellement tous les accès à ses membres isolés.
Lorsque du code extérieur à un actor appelle sa méthode, l'appel est placé dans la file d'attente de l'exécuteur d'actor. L'actor n'exécute qu'une seule tâche à la fois, garantissant l'absence de conditions de course. Si deux threads appellent deposit simultanément, le second appel attend la fin du premier.
let account = BankAccount(initialBalance: 1000.0)
// Appel asynchrone — requis depuis l'extérieur de l'actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
Chaque appel à une méthode d'actor nécessite await car l'actor peut être occupé par une autre tâche. Ce n'est pas un bug, mais une conception délibérée qui empêche les conditions de course. Swift rend implicitement les getters et setters des propriétés d'actor async, donc la lecture d'une propriété nécessite également await.
Actor et classe sont tous deux des types de référence, mais leur comportement dans un environnement multithread diffère radicalement. Une classe n'offre aucune protection automatique contre les conditions de course, tandis qu'un actor l'intègre au niveau du compilateur via le système de types.
| Caractéristique | Actor | Classe |
|---|---|---|
| Protection contre les courses | Automatique, au niveau du compilateur | Nécessite une synchronisation manuelle |
| Accès aux propriétés | Uniquement via await de l'extérieur | Direct, sans synchronisation |
| Héritage | Uniquement d'autres actors | Héritage de classes standard |
| Conformité aux protocoles | Peut être conforme aux protocoles | Standard |
| Performance | Faible surcharge avec isolation | Plus rapide sans synchronisation |
Un actor ne peut hériter que d'un autre actor et ne peut pas hériter d'une classe. C'est intentionnel car une classe n'a pas le mécanisme d'isolation d'actor, et mélanger les types briserait les garanties de sécurité.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
Les appels asynchrones sont le mécanisme d'interaction avec un actor depuis du code externe. Puisqu'un actor isole son état, tout accès à ses membres depuis l'extérieur nécessite await. Cela permet à Swift de garantir que le code appelant ne bloque pas le thread et que l'actor peut traiter d'autres requêtes.
En plus des types d'actor déclarés, Swift prend en charge les acteurs globaux — l'attribut @MainActor, qui marque les classes, propriétés ou méthodes comme exécutables sur le thread principal. Ceci est particulièrement utile pour le code d'interface utilisateur dans les applications iOS.
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// Ce code est garanti de s'exécuter sur le thread principal
title = "New Title"
}
}
L'utilisation de @MainActor élimine le besoin d'appeler manuellement DispatchQueue.main.async, rendant le code plus propre et plus sûr. Le compilateur vérifie que le basculement vers le thread principal se produit correctement.
Nonisolated est un mot-clé qui permet de marquer une méthode ou une propriété calculée d'un actor comme non isolée. Ces membres n'ont pas accès à l'état isolé de l'actor, mais peuvent être appelés sans await depuis l'extérieur de l'actor.
Les méthodes nonisolated sont utiles pour les calculs qui ne dépendent pas de l'état mutable de l'actor. Par exemple, la méthode formatBalance n'accède pas directement à balance, mais formate uniquement la valeur transmise — une telle méthode peut être rendue nonisolated en toute sécurité.
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
Les membres nonisolated s'exécutent de manière synchrone et ne nécessitent pas await. Cependant, ils ne peuvent pas lire directement les propriétés isolées de l'actor. Si une méthode nonisolated a besoin d'une valeur de l'actor, elle doit être transmise en paramètre.
Reentrancy est un mécanisme qui permet la réentrée dans un actor pendant l'attente d'un appel asynchrone. Sans reentrancy, un actor pourrait se bloquer pour toujours si l'une de ses méthodes en attendait une autre qui attendait à son tour la première.
Lorsque le code à l'intérieur d'un actor exécute await, l'actor suspend la tâche en cours et peut traiter une autre tâche en file d'attente. Une fois await terminé, la tâche reprend. Cela évite les blocages mais nécessite de la prudence : l'état de l'actor entre les points await peut changer.
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — point de réentrée
let result = await fetchData(id: id)
// Le cache peut avoir changé après await — revérifier
cache[id] = result
return result
}
}
Les développeurs doivent tenir compte de la réentrée et vérifier l'état de l'actor après les points await. Une erreur typique est de supposer que l'isolation de l'actor persiste à travers les points de suspension asynchrones. En pratique, entre await et l'instruction suivante, l'état peut différer de ce qui était attendu.
Foire Aux Questions
Actor isole automatiquement son état des conditions de course, nécessitant await pour l'accès externe. Une classe n'offre pas une telle protection — le développeur est responsable de la synchronisation via des verrous ou des files d'attente. Un actor n'hérite que d'un actor, une classe d'une classe.
Oui, un actor peut hériter d'un autre actor. La sous-classe reçoit toutes les propriétés et méthodes isolées du parent. Un actor ne peut pas hériter d'une classe car les classes n'ont pas le mécanisme d'isolation d'actor au niveau du compilateur.
Un contexte actor-isolated est une zone de code où l'accès direct à l'état mutable de l'actor est autorisé. À l'intérieur des méthodes d'actor marquées comme isolées (par défaut), on peut lire et écrire des propriétés sans await. Le compilateur vérifie les limites d'isolation.
Les données d'un actor sont transmises via des méthodes async qui retournent des types Sendable, ou via des méthodes nonisolated qui acceptent des valeurs en paramètres. On peut également créer une propriété async qui retourne un instantané de l'état de l'actor sous forme de structure Sendable.
Oui, un actor peut être conforme aux protocoles. Si un protocole contient des exigences isolées (actor-isolated), elles deviennent automatiquement isolées de l'actor. Pour les méthodes asynchrones dans les protocoles, on peut spécifier qu'elles doivent être appelées sur un actor spécifique à l'aide du marqueur isolated.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi