Watchpoint (point d'observation) — un mécanisme de débogage qui suspend l'exécution du programme lorsque la valeur d'une variable ou d'une région mémoire spécifiée change. Contrairement à un breakpoint, qui est lié à une ligne de code, un watchpoint suit les modifications de données indépendamment de l'endroit du programme où la modification se produit. Selon la Apple Developer Documentation, les watchpoints sont indispensables pour déboguer les courses de données, les changements inattendus de propriétés et le suivi des cycles de vie des objets.
Points clés
Watchpoint — est un mécanisme du débogueur qui suspend l'exécution du programme lorsque la valeur à une adresse mémoire spécifiée change. Si un breakpoint réagit à l'atteinte d'une ligne de code spécifique, un watchpoint réagit à l'écriture dans une cellule mémoire spécifique — indépendamment de la partie du code qui a effectué l'écriture. Cela le rend indispensable pour trouver des modifications inattendues de variables, en particulier dans les applications multithread.
Le principe de fonctionnement est basé sur le support matériel du processeur. Les puces ARM modernes (Apple Silicon, Qualcomm Snapdragon) fournissent de 4 à 8 registres matériels de watchpoint. Lorsque le processeur exécute une instruction d'écriture à une adresse correspondant à un watchpoint, une interruption matérielle se produit, que le débogueur intercepte. Lorsque les registres matériels sont épuisés, LLDB passe en mode logiciel — il vérifie pas à pas chaque instruction d'écriture, ralentissant l'exécution de dizaines de fois.
Selon le ARM Architecture Reference Manual, les watchpoints matériels opèrent au niveau du module Data Watchpoint and Trace (DWT) et ne nécessitent pas de modification du code exécutable. Le temps de réponse est de l'ordre de la nanoseconde, permettant de suivre même les changements à haute fréquence dans les boucles de traitement de données comme les tampons audio ou les trames vidéo.
Watchpoint est indispensable lorsque vous savez quelle variable change mais pas d'où. Scénarios typiques : une propriété frame d'UIView change sans raison apparente ; un compteur dans un thread d'arrière-plan se réinitialise de façon imprévisible ; un indicateur isLoading bascule avant qu'une requête réseau ne soit terminée. Dans chacun de ces cas, placer des breakpoints à tous les endroits où une écriture pourrait se produire est peu pratique. Un watchpoint résout le problème avec une seule commande.
Xcode et LLDB prennent en charge trois types de watchpoints : watchpoint set variable — pour surveiller une variable locale ; watchpoint set expression — pour surveiller une expression qui retourne une adresse ; watchpoint set — pour surveiller une adresse mémoire brute. Chaque type a son domaine d'application.
| Type de watchpoint | Commande LLDB | Utilisation |
|---|---|---|
| Variable | watchpoint set variable -w write self.count | Variables locales et globales, propriétés de structures |
| Expression | watchpoint set expression -w write -- &self->mutex.lock | Champs de structures par pointeur, éléments de tableau par index |
| Adresse | watchpoint set -w write 0x600000c4b80 | Adresse mémoire spécifique d'une sortie précédente de LLDB |
Dans Xcode, vous pouvez configurer un watchpoint via la zone de débogage : arrêtez-vous sur un breakpoint, trouvez la variable souhaitée dans le panneau Variables View, faites un clic droit et sélectionnez Watch Variable. Xcode exécutera automatiquement la commande watchpoint set variable avec le nom de variable et le contexte corrects. Après cela, le débogueur s'arrêtera à chaque changement de valeur — pratique pour un débogage rapide sans basculer vers la console. Cependant, cette méthode ne fonctionne que tant que la variable est dans la portée du frame actuel.
func processItems(_ items: [String]) {
var index = 0
// Définir un watchpoint sur l'index via l'interface graphique :
// arrêtez-vous ici, clic droit → Watch Variable
for item in items {
index += 1
print("Élément \(index) : \(item)")
}
}
LLDB fournit un ensemble complet de commandes pour gérer les watchpoints depuis la console. Cela offre plus de contrôle que l'interface graphique : vous pouvez définir la taille de la région d'observation, spécifier des conditions de déclenchement, créer des watchpoints sur des adresses obtenues à partir d'expressions calculées et exécuter automatiquement des actions lors du déclenchement. L'interface en ligne de commande est particulièrement utile pour déboguer des scénarios complexes nécessitant de changer rapidement les paramètres d'observation.
La commande watchpoint set variable prend un nom de variable en tenant compte de la portée : pour les propriétés self d'Objective-C, utilisez self->_property, pour Swift, utilisez self.property. Le paramètre -w write définit le suivi d'écriture, -w read — le suivi de lecture (un mode disponible uniquement sur certaines architectures), -s size — la taille de la région en octets. Après avoir défini un watchpoint, vous pouvez afficher la liste avec la commande watchpoint list.
(lldb) watchpoint set variable -w write -s 8 self.balance
Watchpoint 1: addr = 0x600000c4b80 size = 8 state = enabled type = w
watchpoint spec: 'self.balance'
(lldb) watchpoint set expression -w write -- self->items._storage
Watchpoint 2: addr = 0x600003a4c00 size = 8
(lldb) watchpoint list
1: location = 0x600000c4b80, type = write, variable = '_balance'
2: location = 0x600003a4c00, type = write, expression = 'self->items._storage'
Comme les breakpoints, les watchpoints prennent en charge le déclenchement conditionnel. Le paramètre -c définit une condition en Swift ou Objective-C. Par exemple, un watchpoint avec la condition newValue > 1000 ne s'arrêtera que lorsque la valeur écrite dépasse mille. Ceci est crucial lors du débogage de boucles ou de capteurs générant des milliers de changements par seconde — sinon le débogueur s'arrêterait à chaque changement, rendant le travail impossible.
(lldb) watchpoint set variable -w write self.temperature -c "(int)$newValue > 100"
Watchpoint 3: addr = 0x600000e4a20, condition = '(int)$newValue > 100'
(lldb) watchpoint modify 3 -C "po self.temperature" -G true
// Journaliser automatiquement la valeur et continuer l'exécution
Une caractéristique spécifique des watchpoints pour les propriétés d'objets Objective-C et Swift — un watchpoint est défini non pas sur le nom de la propriété mais sur l'adresse du champ ivar (variable d'instance) dans la mémoire de l'objet. Cela signifie qu'à chaque nouvelle allocation d'objet (par exemple, lors de la recréation d'un ViewController), le watchpoint devient invalide car l'adresse mémoire a changé. Pour une surveillance persistante d'une propriété entre les redémarrages, vous devez redéfinir le watchpoint au moment de l'initialisation de l'objet.
La surveillance d'éléments de tableaux et de dictionnaires nécessite de calculer l'adresse d'un élément spécifique. Par exemple, pour surveiller le troisième élément d'un tableau, utilisez watchpoint set expression -- &array[2]. Si le tableau réalloue son tampon interne (en ajoutant des éléments au-delà de la capacité), le watchpoint devient invalide — LLDB signalera une erreur Watchpoint 1 has an invalid address. Dans ces cas, vous devez redéfinir le watchpoint après avoir modifié la taille de la collection.
(lldb) expr var $arr = [10, 20, 30, 40, 50]
(lldb) watchpoint set expression -w write -- &$arr[2]
Watchpoint 4: addr = 0x1000a4b20, size = 8
(lldb) expr $arr[2] = 99
Watchpoint 4 hit: old value: 30, new value: 99
En Objective-C, vous pouvez suivre le retain count d'un objet en définissant un watchpoint sur le champ retainCount de la structure objc_object. LLDB le fait via watchpoint set expression -w write -- (int*)[object retainCount]. Cependant, pour les objets Swift avec ARC (Automatic Reference Counting), le retain count n'est pas directement accessible — utilisez plutôt Instruments ou le Memory Graph Debugger pour l'analyse des fuites. Selon Apple, les watchpoints sur le retain count ne fonctionnent que dans les builds Debug avec l'optimisation ARC désactivée.
Les watchpoints ont plusieurs limitations importantes à considérer. La principale — le nombre de watchpoints matériels est limité à 4–8 registres sur l'architecture ARM et jusqu'à 4 sur x86. Lorsque tous les registres matériels sont occupés, LLDB passe en mode watchpoint logiciel : il modifie chaque instruction d'écriture dans la plage surveillée pour générer une exception. Cela ralentit l'exécution de 10 à 50 fois, donc en pratique il est recommandé d'utiliser pas plus de 2–3 watchpoints actifs simultanément.
La deuxième limitation — l'invalidation des watchpoints lors de la réallocation mémoire. Lorsque le ramasse-miettes ou ARC se déclenche, et qu'un objet se déplace en mémoire (dans les langues avec compactage du tas), l'adresse du watchpoint devient incorrecte. En Swift et Objective-C, ARC ne déplace pas les objets, mais la réallocation de tableaux et de chaînes provoque le même effet. LLDB en avertit avec le message Watchpoint N address (0x...) doesn’t contain a valid allocation.
La troisième limitation — la visibilité des variables. Un watchpoint sur une variable locale ne fonctionne que tant que cette variable est dans la portée du frame de pile actuel. Dès que la fonction se termine, le watchpoint est automatiquement supprimé. Pour surveiller des variables globales ou des champs d'objets à longue durée de vie, un watchpoint persiste jusqu'à sa suppression explicite via watchpoint delete ou jusqu'à la fin du processus.
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # Supprimer tous les watchpoints
2 watchpoints deleted.
(lldb) watchpoint disable 1 # Désactiver temporairement
(lldb) watchpoint enable 1 # Réactiver
Selon ARM, les watchpoints sur Apple Silicon (M1–M4) prennent en charge une taille de région surveillée de 1 à 8 octets. Pour surveiller des structures de plus de 8 octets, vous devez définir plusieurs watchpoints sur chaque champ. Ceci est important lors du débogage de structures de données complexes comme CGRect (16 octets) ou UIEdgeInsets (16 octets).
Questions fréquentes
Breakpoint est lié à une ligne de code — il s'arrête lorsque cette ligne est atteinte. Watchpoint est lié à une adresse mémoire — il s'arrête lorsqu'une écriture se produit à cette adresse depuis n'importe où dans le programme. Un watchpoint cherche « qui change la valeur », un breakpoint cherche « ce qui se passe sur cette ligne ».
4–8 watchpoints matériels sur ARM (y compris Apple Silicon). Le dépassement fait passer le watchpoint en mode logiciel, ralentissant l'exécution de 10 à 50 fois. Il est recommandé de ne pas utiliser plus de 2–3 watchpoints actifs à la fois.
Les watchpoints ne sont pas conservés entre les sessions de débogage. À chaque nouveau lancement, les adresses mémoire changent et le watchpoint doit être redéfini. L'exception concerne les watchpoints sur les variables globales avec une adresse fixe.
Oui, mais le watchpoint est défini sur l'ivar (stockage de soutien) de la propriété, pas sur la propriété elle-même. En Swift, utilisez watchpoint set variable self.property — LLDB trouvera automatiquement l'ivar correspondant par le nom de la propriété.
Les watchpoints matériels n'affectent pas les performances — l'interruption se produit au niveau du processeur. Les watchpoints logiciels (lorsque les registres matériels sont épuisés) ralentissent l'exécution de 10 à 50 fois, car LLDB vérifie chaque instruction d'écriture.
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