Weak Reference (référence faible) est une référence à un objet qui n'augmente pas son compteur de rétention dans ARC. Selon Apple Swift Language Guide, 2026, les références faibles sont déclarées avec le mot-clé weak et sont toujours optionnelles. Lorsque l'objet est libéré, toutes les références faibles vers lui sont automatiquement définies à nil, ce qui empêche les pointeurs pendants et fait des références faibles un mécanisme sûr pour briser les cycles de rétention.
Points clés
weak avant var ; type toujours optionnel (?)Weak Reference est une référence non propriétaire à un objet dans ARC (Automatic Reference Counting). Contrairement à une référence forte, qui augmente le retain count de l'objet et garantit sa durée de vie, une référence faible permet à l'objet d'être libéré même s'il est toujours référencé. Après la désallocation, la référence faible est automatiquement définie à nil — cela s'appelle zeroing weak.
Zeroing weak est une fonctionnalité clé de l'environnement d'exécution Swift et Objective-C. Lorsque le compteur de références d'un objet atteint zéro et que l'objet est désalloué, l'environnement d'exécution parcourt toutes les références faibles vers cet objet (stockées dans une table faible spéciale) et les définit à nil. Cela garantit que l'accès à la mémoire libérée (use-after-free) est impossible via les références faibles — toute lecture retourne nil.
Selon Apple WWDC 2012 Session 406, les références faibles zeroing ont éliminé toute une classe de bogues de crash liés aux pointeurs pendants (dangling pointers), qui étaient courants dans la gestion manuelle de la mémoire (MRR). En MRR, les références faibles n'existaient que sous forme de __unsafe_unretained — elles ne se mettaient pas à zéro, et l'accès à un objet désalloué entraînait EXC_BAD_ACCESS.
Examinons la syntaxe pour déclarer des références faibles dans les deux langages de l'écosystème Apple. Malgré l'environnement d'exécution partagé, la syntaxe diffère, mais la sémantique est identique.
En Swift, les références faibles sont déclarées avec le mot-clé weak avant var. Le type doit toujours être optionnel (Type?), car la référence peut devenir nil à tout moment. Les constantes (let) ne peuvent pas être weak — seulement les variables.
class ViewController: UIViewController {
// weak properties: only var, only optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ les closures ne stockent pas weak
// ⬆️ Erreur : weak ne peut être appliqué qu'aux types class, pas aux closures
}
Important : weak s'applique uniquement aux instances de classe (types class), AnyObject et aux protocoles hérités de AnyObject. Struct, enum et closures ne peuvent pas être weak — ce sont des types valeur et ils ne participent pas à ARC.
En Objective-C, les propriétés faibles sont déclarées à l'aide de l'attribut __weak ou du modificateur weak dans les déclarations de propriété :
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Variable locale faible
__weak MyObject *weakRef = someStrongObject;
L'environnement d'exécution Objective-C fournit également zeroing weak, mais bloque en outre l'utilisation de weak avec les structures C et certains objets Core Foundation. Pour ceux-ci, on utilise __unsafe_unretained — sans zeroing.
Les références faibles ne sont pas une solution universelle, mais un outil pour des scénarios spécifiques. Utiliser weak partout conduit à une complexité inutile et nuit à la lisibilité. Examinons les scénarios d'utilisation corrects.
Délégués — le scénario principal pour weak. L'objet propriétaire (par exemple, UITableView) maintient une référence forte sur lui-même, tandis que le délégué (UIViewController) ne doit pas posséder la table. Le SDK Apple garantit que tous les delegates et dataSources sont weak. Pour vos propres protocoles, utilisez toujours weak var delegate.
Lorsqu'un objet enfant doit référencer son parent (par exemple, ChildViewController accédant à un coordinateur), utilisez une référence faible. Le parent possède l'enfant (strong), l'enfant observe le parent (weak) — le cycle de rétention est éliminé.
Capture list [weak self] — la manière standard d'éviter les cycles de rétention dans les closures stockées comme propriétés de classe. Si self peut être désalloué avant la fin de la closure, weak self est obligatoire.
| Scénario | Weak | Strong |
|---|---|---|
| Délégué | ✅ Toujours weak | ❌ Retain cycle |
| Parent → Enfant | ❌ Pas nécessaire (parent doit posséder) | ✅ Strong |
| Enfant → Parent | ✅ Weak | ❌ Retain cycle |
| Callback asynchrone | ✅ [weak self] | ❌ Risque de retain cycle |
| Couplage fort (owned) | ❌ unowned | ✅ Strong |
Règle générale : si l'objet A possède B (A → B strong), alors B → A doit être weak ou unowned. La direction des références fortes doit toujours être du propriétaire vers le subordonné.
Weak et unowned n'augmentent ni l'un ni l'autre le retain count, mais diffèrent dans leur comportement après la désallocation de l'objet. Le choix entre eux est une question de garanties de durée de vie.
Weak : devient nil automatiquement, le type est toujours optionnel, nécessite un déballage avant utilisation. Sûr — l'accès à nil ne provoque pas de crash.
Unowned : ne devient pas nil, le type est non optionnel. Si l'objet est désalloué, une référence unowned devient un pointeur pendant — y accéder provoque un crash à l'exécution. Unowned suppose que l'objet vit au moins aussi longtemps que le côté référençant.
Choisissez weak si : l'objet peut être désalloué à tout moment (délégué après fermeture de l'écran), vous ne contrôlez pas la durée de vie de l'objet, ou vous doutez des garanties. Weak est le choix sûr universel.
Choisissez unowned si : l'objet est garanti de ne pas être désalloué avant l'objet référençant (par exemple, Client → CarteCredit, où la carte n'existe pas sans le client). Unowned fournit une API non optionnelle sans déballage, ce qui est plus pratique dans le code.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Relation forte : Order possède Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item ne vit pas sans Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Exemple avec weak : délégué sans garantie de durée de vie
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — le délégué peut disparaître
}
Dans l'exemple, Item utilise unowned car un article de commande ne peut pas exister sans la commande elle-même — la garantie de durée de vie est inébranlable. NetworkService utilise weak car le délégué (par exemple, ViewController) peut être fermé et désalloué à tout moment.
Les références faibles sont un outil puissant, mais elles ont des limites qu'il est important de comprendre pour une utilisation correcte dans le développement iOS.
Les références faibles sont plus lentes que les fortes : à chaque accès, l'environnement d'exécution vérifie si l'objet a été désalloué (lookup dans la table faible). Dans la grande majorité des scénarios, la différence est imperceptible, mais dans les boucles critiques avec des millions d'accès, weak peut devenir un goulot d'étranglement. Pour les scénarios à forte charge, utilisez strong et réorganisez l'architecture.
Struct, enum, tuple — des types valeur qui ne participent pas à ARC. Tenter de déclarer un weak struct provoque une erreur de compilation. Pour stocker une référence faible à un type valeur, utilisez un wrapper dans un type classe ou une closure.
Zeroing weak est thread-safe : si un objet est désalloué sur un thread, la référence faible est remise à zéro sur tous les threads de manière atomique. Cependant, la fenêtre entre la lecture d'une référence faible et son déréférencement peut entraîner une condition de course — l'objet est désalloué entre l'obtention de la référence faible et son utilisation. Solution : capture forte de la référence faible dans une variable locale.
// Race condition avec weak en multithreading
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf peut être nil entre la vérification et l'utilisation
if weakSelf != nil {
weakSelf!.doSomething() // CRASH s'il devient nil
}
}
}
// ✅ Correction : capture forte pendant l'utilisation
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — référence locale forte
}
}
Dans la version sûre, weak self est capturé, puis immédiatement déballé dans une variable locale forte strongSelf. Si self est toujours vivant, il restera vivant pendant la durée du bloc. Sinon, guard se déclenche et le code ne s'exécute pas. Cet idiome est le modèle standard pour les closures asynchrones en Swift.
IBOutlet dans Interface Builder doivent être weak car la hiérarchie des vues maintient déjà une référence forte à la sous-vue. Dupliquer une référence forte dans le contrôleur ne crée pas de cycle de rétention mais est redondant. Une référence faible à un outlet est la recommandation d'Apple, bien que de nombreux développeurs utilisent strong pour simplifier le code.
Questions fréquentes
Non, weak ne peut pointer que vers un objet existant ou nil. Lors de la création d'un nouvel objet, vous obtenez d'abord une référence forte (via un initialisateur), et ce n'est qu'ensuite que vous pouvez attribuer une référence faible. Un weak nil au début est un état normal.
Weak est basé sur ARC, qui ne gère que les types référence (classes). Les types valeur (struct, enum) sont copiés lors de l'affectation et n'ont pas de retain count. Pour les relations faibles avec les types valeur, utilisez des closures ou des wrappers dans une classe avec une propriété weak.
Chaque accès à une référence faible effectue un lookup dans la table d'exécution. Dans une boucle de millions d'itérations, cela peut être 2 à 5 fois plus lent qu'une référence forte. Pour les chemins chauds, copiez weak dans une variable locale forte avant la boucle.
Lorsque toutes les références fortes vers l'objet sont perdues — à la fin de la portée, lors de la réaffectation d'une propriété, ou lors de la fermeture d'un écran. Dans un environnement multithread, cela peut se produire entre deux lignes de code. Vérifiez toujours les références faibles avec guard let ou if let.
Sémantiquement identiques : les deux fournissent zeroing weak. Différences : Swift nécessite un type optionnel et var, Objective-C utilise un modificateur de propriété. Objective-C prend également en charge __unsafe_unretained — une référence faible sans zeroing (risque de pointeur pendant).
Résumé
weak var + type optionnel ; uniquement les types class et les protocoles AnyObjectNous 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