Watchpoint (punkt obserwacyjny) — mechanizm debugowania, który wstrzymuje wykonanie programu przy zmianie wartości wskazanej zmiennej lub obszaru pamięci. W przeciwieństwie do breakpointa, który jest przypisany do linii kodu, watchpoint śledzi zmianę danych niezależnie od tego, w którym miejscu programu nastąpiła modyfikacja. Według Apple Developer Documentation, watchpointy są niezastąpione podczas debugowania wyścigów danych, nieoczekiwanych zmian właściwości i śledzenia cyklu życia obiektów.
Najważniejsze
Watchpoint — to mechanizm debuggera, który wstrzymuje wykonanie programu, gdy wartość pod wskazanym adresem pamięci ulegnie zmianie. Jeśli breakpoint reaguje na osiągnięcie określonej linii kodu, to watchpoint reaguje na zapis w określoną komórkę pamięci — niezależnie od tego, który fragment kodu ten zapis wykonał. To czyni go niezastąpionym przy poszukiwaniu nieoczekiwanych modyfikacji zmiennych, szczególnie w aplikacjach wielowątkowych.
Zasada działania opiera się na wsparciu sprzętowym procesora. Nowoczesne układy ARM (Apple Silicon, Qualcomm Snapdragon) udostępniają od 4 do 8 sprzętowych rejestrów watchpoint. Gdy procesor wykonuje instrukcję zapisu pod adresem zgodnym z watchpointem, powstaje przerwanie sprzętowe, które przechwytuje debugger. Jeśli rejestry sprzętowe się wyczerpią, LLDB przełącza się w tryb programowy — krok po kroku sprawdza każdą instrukcję zapisu, co spowalnia wykonanie dziesięciokrotnie.
Według ARM Architecture Reference Manual, sprzętowe watchpointy działają na poziomie modułu Data Watchpoint and Trace (DWT) i nie wymagają modyfikacji kodu wykonywalnego. Czas reakcji na uruchomienie wynosi jednostki nanosekund, co pozwala śledzić nawet wysokoczęstotliwościowe zmiany zmiennych w pętlach przetwarzania danych, takich jak bufory audio czy klatek wideo.
Watchpoint jest niezastąpiony, gdy wiesz, która zmienna się zmienia, ale nie wiesz skąd. Typowe scenariusze: wartość właściwości frame w UIView zmienia się bez widocznej przyczyny; licznik w wątku tła resetuje się nieprzewidywalnie; flaga isLoading przełącza się wcześniej niż zakończyło się żądanie sieciowe. W każdym z tych przypadków ustawianie breakpointa we wszystkich miejscach, gdzie może nastąpić zapis, jest niepraktyczne. Watchpoint rozwiązuje problem jednym poleceniem.
Xcode i LLDB obsługują trzy typy watchpointów: watchpoint set variable — do obserwacji zmiennej lokalnej; watchpoint set expression — do obserwacji wyrażenia zwracającego adres; watchpoint set — do obserwacji surowego adresu pamięci. Każdy typ ma swój obszar zastosowania.
| Typ watchpointa | Polecenie LLDB | Zastosowanie |
|---|---|---|
| Zmienna | watchpoint set variable -w write self.count | Zmienne lokalne i globalne, właściwości struktur |
| Wyrażenie | watchpoint set expression -w write -- &self->mutex.lock | Pola struktur przez wskaźnik, elementy tablic po indeksie |
| Adres | watchpoint set -w write 0x600000c4b80 | Konkretny adres pamięci z poprzedniego wyjścia LLDB |
W Xcode watchpoint można ustawić przez Debug Area: zatrzymaj się na breakpoincie, znajdź potrzebną zmienną w panelu Variables View, kliknij prawym przyciskiem i wybierz Watch Variable. Xcode automatycznie wykona polecenie watchpoint set variable z poprawną nazwą zmiennej i kontekstem. Następnie debugger będzie zatrzymywał się przy każdej zmianie wartości — wygodne do szybkiego debugowania bez przełączania do konsoli. Jednak ta metoda działa tylko gdy zmienna znajduje się w zasięgu widoczności bieżącej ramki.
func processItems(_ items: [String]) {
var index = 0
// Ustaw watchpoint na index przez GUI:
// zatrzymaj się tutaj, kliknij prawym przyciskiem → Watch Variable
for item in items {
index += 1
print("Item \(index): \(item)")
}
}
LLDB udostępnia pełen zestaw poleceń do zarządzania watchpointami z konsoli. Daje to większą kontrolę niż GUI: można określać rozmiar obszaru obserwacji, ustawiać warunek uruchomienia, tworzyć watchpointy na adresy uzyskane z obliczonych wyrażeń i automatycznie wykonywać akcje przy uruchomieniu. Interfejs wiersza poleceń jest szczególnie przydatny podczas debugowania złożonych scenariuszy, gdzie trzeba szybko zmieniać parametry obserwacji.
Polecenie watchpoint set variable przyjmuje nazwę zmiennej z uwzględnieniem zakresu widoczności: dla właściwości self w Objective-C używaj self->_property, dla Swift — self.property. Parametr -w write ustawia śledzenie zapisu, -w read — odczytu (tryb dostępny tylko na niektórych architekturach), -s size — rozmiar obszaru w bajtach. Po ustawieniu watchpointa można wyświetlić listę poleceniem 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'
Podobnie jak breakpointy, watchpointy obsługują warunkowe uruchamianie. Parametr -c określa warunek w Swift lub Objective-C. Na przykład watchpoint z warunkiem newValue > 1000 zatrzyma się tylko gdy zapisywana wartość przekroczy tysiąc. Jest to krytyczne przy debugowaniu pętli lub czujników generujących tysiące zmian na sekundę — w przeciwnym razie debugger zatrzymywałby się przy każdej zmianie, uniemożliwiając pracę.
(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
// Automatycznie loguj wartość i kontynuuj wykonanie
Osobliwość watchpointów dla właściwości obiektów Objective-C i Swift — watchpoint ustawia się nie na nazwę właściwości, ale na adres pola ivar (instance variable) w pamięci obiektu. Oznacza to, że przy każdym nowym przydzieleniu obiektu (np. przy odtwarzaniu ViewController) watchpoint staje się nieaktualny, ponieważ adres pamięci się zmienił. Do stałej obserwacji właściwości przez restarty wymagane jest ponowne ustawienie watchpointa w momencie inicjalizacji obiektu.
Obserwacja elementów tablic i słowników wymaga obliczenia adresu konkretnego elementu. Na przykład do obserwacji trzeciego elementu tablicy użyj watchpoint set expression -- &array[2]. Jeśli tablica ponownie przydzieli wewnętrzny bufor (przy dodawaniu elementów powyżej capacity), watchpoint stanie się nieaktualny — LLDB zgłosi błąd Watchpoint 1 has an invalid address. W takich przypadkach trzeba ponownie ustawić watchpoint po zmianie rozmiaru kolekcji.
(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
W Objective-C można śledzić retain count obiektu, ustawiając watchpoint na pole retainCount w strukturze objc_object. LLDB wykonuje to przez watchpoint set expression -w write -- (int*)[object retainCount]. Jednak dla obiektów Swift z ARC (Automatic Reference Counting) retain count nie jest bezpośrednio dostępny — zamiast tego używaj Instruments lub Memory Graph Debugger do analizy wycieków. Według Apple, watchpointy na retain count działają tylko w kompilacjach Debug z wyłączoną optymalizacją ARC.
Watchpointy mają szereg ograniczeń, które należy uwzględnić. Główne — liczba sprzętowych watchpointów jest ograniczona do 4–8 rejestrów w architekturze ARM i do 4 w x86. Gdy wszystkie rejestry sprzętowe są zajęte, LLDB przełącza się w tryb programowego watchpointa: modyfikuje każdą instrukcję zapisu w śledzonym zakresie, aby wygenerować wyjątek. Spowalnia to wykonanie 10–50 razy, dlatego w praktyce zaleca się używanie nie więcej niż 2–3 aktywnych watchpointów jednocześnie.
Drugie ograniczenie — nieaktualność watchpointów przy ponownym przydzielaniu pamięci. Podczas działania garbage collectora lub ARC, gdy obiekt jest przenoszony w pamięci (w językach z kompakcją sterty), adres watchpointa staje się nieprawidłowy. W Swift i Objective-C ARC nie przenosi obiektów, ale realokacja tablic i łańcuchów prowadzi do tego samego efektu. LLDB ostrzega o tym komunikatem Watchpoint N address (0x...) doesn't contain a valid allocation.
Trzecie ograniczenie — widoczność zmiennych. Watchpoint na zmienną lokalną działa tylko dopóki ta zmienna znajduje się w zasięgu widoczności bieżącej ramki stosu. Po zakończeniu funkcji watchpoint jest automatycznie usuwany. Do obserwacji zmiennych globalnych lub pól długożyciowych obiektów watchpoint pozostaje aktywny do jawnego usunięcia przez watchpoint delete lub do zakończenia procesu.
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # Usuń wszystkie watchpoints
2 watchpoints deleted.
(lldb) watchpoint disable 1 # Tymczasowo wyłącz
(lldb) watchpoint enable 1 # Włącz ponownie
Według ARM, watchpointy na Apple Silicon (M1–M4) obsługują rozmiar śledzonego obszaru od 1 do 8 bajtów. Do obserwacji struktur o rozmiarze większym niż 8 bajtów wymagane jest ustawienie kilku watchpointów na każde pole. Jest to ważne przy debugowaniu złożonych struktur danych, takich jak CGRect (16 bajtów) czy UIEdgeInsets (16 bajtów).
Często zadawane pytania
Breakpoint jest przypisany do linii kodu — zatrzymuje po jej osiągnięciu. Watchpoint jest przypisany do adresu pamięci — zatrzymuje przy zapisie pod ten adres z dowolnego miejsca programu. Watchpoint szuka „kto zmienia wartość", breakpoint — „co się dzieje w tej linii".
4–8 sprzętowych na ARM (w tym Apple Silicon). Przekroczenie przełącza watchpoint w tryb programowy, co spowalnia wykonanie 10–50 razy. Zaleca się nie więcej niż 2–3 aktywne watchpointy jednocześnie.
Watchpointy nie są przechowywane między sesjami debugowania. Przy każdym nowym uruchomieniu adresy pamięci się zmieniają i watchpoint trzeba ustawić ponownie. Wyjątkiem są watchpointy na zmienne globalne o stałym adresie.
Tak, ale watchpoint ustawia się na ivar (backing storage) właściwości, a nie na samą właściwość. W Swift użyj watchpoint set variable self.property — LLDB automatycznie znajdzie odpowiedni ivar po nazwie właściwości.
Sprzętowy watchpoint nie wpływa na wydajność — przerwanie następuje na poziomie procesora. Programowy (przy wyczerpaniu rejestrów sprzętowych) spowalnia wykonanie 10–50 razy, ponieważ LLDB sprawdza każdą instrukcję zapisu.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również