Memory Graph est un outil visuel du Xcode Debug Navigator qui affiche un graphe des objets dans la mémoire de l'application avec leurs références mutuelles. Contrairement à un heap dump, Memory Graph montre non seulement une liste d'objets, mais un graphe orienté de références où chaque nœud est un objet et chaque arête est une référence (strong, weak, unowned). Selon Apple WWDC 2018, l'outil permet de détecter visuellement les retain cycles et les fuites mémoire en quelques secondes, sans avoir à analyser les données brutes du heap dump.
Points clés
Memory Graph est un composant du Xcode Debug Navigator (introduit dans Xcode 10, WWDC 2018) qui construit un graphe orienté de tous les objets dans la mémoire du processus débogué. Chaque nœud du graphe est une instance de classe (Objective-C ou Swift), chaque arête est une référence vers un autre objet. La couleur de l'arête indique le type de référence : bleu — strong, vert — weak, gris — unowned. Le graphe est construit à partir des données LLDB et de l'Objective-C runtime, donc l'application doit être compilée en configuration Debug avec les symboles activés pour un fonctionnement correct.
Comment ça fonctionne : lorsque l'application est en pause sur un breakpoint, Xcode demande via LLDB au runtime tous les objets vivants et leurs références. LLDB utilise objc_getClassList et itère sur les régions d'allocation pour construire le graphe complet. Sur ARM64 (Apple Silicon), des moyens matériels supplémentaires sont utilisés pour le suivi des allocations sans ralentissement. Le temps de construction du graphe dépend de la taille du heap : pour une application iOS typique (50–200 Mo), le graphe se construit en 1–3 secondes.
Selon Apple, Memory Graph est le seul outil qui peut visualiser les retain cycles sans modification de code ni ajout d'instrumentation. Contrairement à Instruments Leaks, Memory Graph fonctionne en temps réel dans Xcode et ne nécessite pas de lancement séparé du profileur. Cela en fait l'outil de premier choix pour le diagnostic rapide des fuites mémoire pendant le développement.
Heap dump fournit un tableau de tous les objets avec des chiffres (shallow size, retained size) — il est optimal pour l'analyse quantitative. Memory Graph fournit une image visuelle des connexions — optimal pour trouver les références cycliques. Les outils se complètent : d'abord Memory Graph pour la détection rapide des retain cycles, puis heap dump via Instruments Allocations pour la mesure précise du retained size. Selon objc.io, la combinaison des deux méthodes couvre 95 % des scénarios de fuites mémoire.
Retain cycle est une situation où deux objets ou plus se retiennent mutuellement avec des références fortes, formant une boucle fermée. ARC ne peut pas désallouer une telle boucle car le retain count de chaque objet n'atteint jamais zéro. Un exemple classique : ViewController et View, où View a une référence forte vers une closure qui capture self (ViewController). Memory Graph affiche ces boucles comme des anneaux (cycles), les mettant en évidence pour une identification rapide.
Lorsque Xcode détecte un retain cycle, il le surligne avec un contour orange et affiche un avertissement dans le Debug Navigator. En cliquant sur le cycle, vous voyez la chaîne de références formant la boucle fermée. Le développeur n'a qu'à déterminer quelle arête strong devrait être weak — généralement c'est une référence d'un objet enfant vers le parent (par exemple, delegate ou closure).
class ViewController: UIViewController {
let service = DataService()
override func viewDidLoad() {
super.viewDidLoad()
// ❌ Retain cycle: ViewController → service → closure → ViewController
service.fetchData { self.updateUI($0) }
}
func updateUI(_ data: Data) {}
}
class DataService {
var completion: ((Data) -> Void)?
func fetchData(handler: @escaping (Data) -> Void) {
self.completion = handler
}
}
Dans Memory Graph, vous verrez un triangle : ViewController → DataService → closure → ViewController. La solution est de rendre la capture de self faible : [weak self]. Après la correction, Memory Graph montrera une arête verte de la closure vers ViewController, et le retain cycle disparaîtra.
// Code corrigé — capture faible de self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
L'interface de Memory Graph Debugger se compose de trois panneaux : le gauche — une liste de tous les objets vivants (groupés par classe) avec le nombre d'instances ; le central — un graphe visuel avec des nœuds déplaçables ; le droit — un inspecteur de l'objet ou de l'arête sélectionné. La liste d'objets affiche : l'icône de la classe, le nombre d'instances en mémoire, le retained size total et le pourcentage de tout le heap. Le filtrage par nom de classe prend en charge les expressions régulières.
Les nœuds du graphe peuvent être déplacés pour améliorer la lisibilité. Un double-clic sur un nœud ouvre des informations détaillées sur l'objet : toutes ses propriétés avec types et valeurs, la pile d'appels (backtrace) pour chaque propriété et l'historique retain/release. Backtrace est une fonctionnalité clé : elle montre quelle ligne de code exacte a établi la référence vers l'objet. Cela permet de trouver la source de la fuite sans examiner manuellement tout le code.
Pour les graphes complexes, Xcode fournit une disposition automatique via Layout → Hierarchical ou Cluster. La disposition hiérarchique place les objets racine en haut et les enfants en bas, simplifiant la recherche de chaînes. Le regroupement en clusters groupe les objets apparentés, ce qui est pratique lorsque le graphe contient plusieurs groupes isolés. Selon Apple, pour la plupart des applications, la disposition hiérarchique est recommandée — elle est intuitive et prend moins de temps pour l'analyse visuelle.
// Commandes LLDB utilisées par Memory Graph en interne
(lldb) script import lldb.macosx.heap
(lldb) script heap.find_variable("viewController")
0x600000c4b80: ViewController
(lldb) script heap.refs 0x600000c4b80
0x600000c4b80 -> 0x600003a4c00 (DataService)
ivar: _service, offset: 16
Une approche systématique de l'analyse de Memory Graph comprend plusieurs étapes. Étape 1 : exécutez l'application, effectuez un scénario qui pourrait causer une fuite (ouvrir/fermer un écran, faire une requête réseau). Étape 2 : cliquez sur le bouton Memory Graph dans Debug Navigator — Xcode construit le graphe. Étape 3 : vérifiez les avertissements orange de retain cycles dans le panneau gauche. Étape 4 : pour les objets suspects, utilisez l'option Show only cycles — seuls les nœuds impliqués dans des références cycliques seront affichés.
Une fois un retain cycle trouvé, cliquez sur l'arête du cycle et ouvrez le panneau de l'inspecteur. La section Backtrace montre la pile d'appels au moment où cette référence a été établie. Par exemple, si l'arête va d'une closure à self, le backtrace montrera dans quelle méthode et sur quelle ligne de code la closure a été créée. Cela élimine le besoin de deviner — vous voyez immédiatement le point où la référence problématique a été créée. Selon WWDC Labs, l'analyse du backtrace réduit le temps de diagnostic d'un retain cycle de 15–20 minutes à 2–3 minutes.
class ProfileViewController: UIViewController {
var profileView: ProfileView!
override func viewDidLoad() {
super.viewDidLoad()
profileView = ProfileView()
// Memory Graph montrera le retain cycle ici
profileView.onTap = { [unowned self] in
// ⚠️ unowned peut causer un crash quand self est nil
self.navigateToDetail()
}
}
func navigateToDetail() { }
}
// ✅ Correct : [weak self] + guard let self
profileView.onTap = { [weak self] in
guard let self else { return }
self.navigateToDetail()
}
Memory Graph peut afficher des milliers d'objets, rendant la recherche difficile. Utilisez les filtres dans le panneau gauche : entrez un nom de classe (par exemple, ProfileViewController) pour afficher uniquement les instances de cette classe. Ensuite, sélectionnez une instance qui aurait dû être désallouée (si l'écran est fermé mais que l'objet reste). Appliquez Show Reachable From — seules les références pertinentes pour cet objet seront affichées, masquant le reste du graphe.
Les développeurs expérimentés utilisent Memory Graph non seulement pour trouver des fuites, mais aussi pour le contrôle proactif de la mémoire. Vérifiez Memory Graph après chaque changement architectural majeur — ajout d'un nouveau delegate, closure ou abonnement NotificationCenter. Il suffit d'exécuter un scénario typique et de s'assurer que les objets sont correctement désalloués et qu'il n'y a pas de retain cycles. Cela prend 2–3 minutes, mais évite des heures de débogage ultérieur.
Memory Report dans Xcode (onglet Debug Navigator) montre un graphique de la consommation mémoire en temps réel. Utilisez-le avec Memory Graph : ouvrez Memory Graph lors d'un pic de consommation. Par exemple, en faisant défiler une longue liste avec des cellules chargeant des images, Memory Graph montrera quels objets sont créés et lesquels sont désalloués. Si le nombre d'objets augmente sans diminuer — c'est une fuite potentielle visible avant qu'elle ne provoque un crash. Selon Apple, la combinaison Memory Graph + Memory Report est le workflow recommandé pour tous les développeurs iOS à partir de Xcode 12.
// Exemple de fuite en Objective-C via delegation
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ Doit être weak !
@end
@implementation DownloadManager
// Memory Graph montrera le retain cycle :
// ViewController → DownloadManager.delegate → ViewController
@end
// Correction : weak property
@property (weak) id delegate;
Portez une attention particulière aux closures — la source la plus fréquente de retain cycles en Swift. Lors de la capture de self dans une closure stockée comme propriété d'un objet, un cycle classique se forme. Memory Graph l'affiche comme une closure (un nœud avec le symbole {}) reliée par des arêtes bleues aux objets capturés. Vérifiez régulièrement toutes les closures, surtout celles utilisées dans les appels asynchrones, GCD, Combine et SwiftUI. Selon les statistiques de Point-Free, 90 % des fuites dans les projets Swift sont liées aux closures qui capturent self.
Questions fréquentes
Memory Graph fonctionne pour les deux langues car il utilise l'Objective-C runtime. Les objets Swift compatibles ObjC (sous-classes de NSObject marquées @objc) sont entièrement affichés. Les structures et classes Swift pures sans pont ObjC sont vues avec des limitations.
Les objets doivent être enregistrés dans l'Objective-C runtime. Les types valeur Swift (struct, enum) ne sont pas affichés. Assurez-vous que la classe hérite de NSObject ou utilise l'attribut @objc pour être visible dans Memory Graph.
Bleu — référence strong, retient l'objet. Vert — référence weak, n'affecte pas le cycle de vie. Gris — référence unowned. Un retain cycle se forme uniquement avec des arêtes bleues.
La construction du graphe met en pause l'application pendant 1–3 secondes et peut augmenter temporairement la consommation mémoire de Xcode de 200–500 Mo. L'application elle-même n'est pas ralentie car l'inspection a lieu pendant la pause du breakpoint.
Xcode ne prend pas en charge l'exportation directe du graphe. Utilisez une capture d'écran pour la documentation ou le script lldb heap.find_variable pour l'extraction programmatique des données. Pour une analyse détaillée, utilisez Instruments Allocations avec un heap dump.
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