Watchpoint (тачка посматрања) — механизам отклањања грешака који зауставља извршење програма при промени вредности наведене променљиве или меморијске области. За разлику од breakpoint-а, који је везан за линију кода, watchpoint прати промену података без обзира на то у ком делу програма је дошло до измене. Према Apple Developer Documentation, watchpoint-ови су незаменљиви при отклањању грешака у тркама података, неочекиваним променама својстава и праћењу животног циклуса објеката.
Главно
Watchpoint — механизам дебагера који зауставља извршење програма када се промени вредност на наведеној меморијској адреси. Ако breakpoint реагује на достизање одређене линије кода, watchpoint реагује на упис у одређену меморијску ћелију — без обзира на то који део кода је извршио тај упис. То га чини незаменљивим при тражењу неочекиваних модификација променљивих, посебно у вишенитним апликацијама.
Принцип рада заснива се на хардверској подршци процесора. Савремени ARM чипови (Apple Silicon, Qualcomm Snapdragon) пружају од 4 до 8 хардверских watchpoint регистара. Када процесор изврши инструкцију уписа на адресу која се поклапа са watchpoint-ом, настаје хардверски прекид који дебагер пресреће. Ако хардверски регистри понестану, LLDB прелази у софтверски режим — проверава сваку инструкцију уписа корак по корак, што успорава извршење десетинама пута.
Према ARM Architecture Reference Manual, хардверски watchpoint-ови раде на нивоу модула Data Watchpoint and Trace (DWT) и не захтевају измену извршног кода. Време реакције на активирање износи јединице наносекунди, што омогућава праћење чак и високофреквентних промена променљивих у петљама обраде података, попут аудио бафера или видео оквира.
Watchpoint је незаменљив када знате која се променљива мења, али не знате одакле. Типични сценарији: вредност својства frame у UIView мења се без видљивог разлога; бројач у позадинској нити ресетује се непредвидиво; заставица isLoading пребацује се раније него што се завршио мрежни захтев. У сваком од ових случајева, постављање breakpoint-а на сва места где може доћи до уписа је непрактично. Watchpoint решава проблем једном командом.
Xcode и LLDB подржавају три врсте watchpoint-ова: watchpoint set variable — за посматрање локалне променљиве; watchpoint set expression — за посматрање израза који враћа адресу; watchpoint set — за посматрање сирове меморијске адресе. Свака врста има своју област примене.
| Врста watchpoint-а | LLDB команда | Примена |
|---|---|---|
| Променљива | watchpoint set variable -w write self.count | Локалне и глобалне променљиве, својства структура |
| Израз | watchpoint set expression -w write -- &self->mutex.lock | Поља структура преко показивача, елементи низова по индексу |
| Адреса | watchpoint set -w write 0x600000c4b80 | Конкретна меморијска адреса из претходног излаза LLDB |
У Xcode-у watchpoint се може поставити преко Debug Area: зауставите се на breakpoint-у, пронађите потребну променљиву у панелу Variables View, кликните десним тастером и изаберите Watch Variable. Xcode аутоматски извршава команду watchpoint set variable са исправним именом променљиве и контекстом. Након тога дебагер ће се заустављати при свакој промени вредности — погодно за брзо отклањање грешака без преласка на конзолу. Међутим, овај метод ради само док је променљива у домену видљивости тренутног оквира.
func processItems(_ items: [String]) {
var index = 0
// Поставите watchpoint на index преко GUI-ја:
// зауставите се овде, десни клик → Watch Variable
for item in items {
index += 1
print("Item \(index): \(item)")
}
}
LLDB пружа комплетан скуп команди за управљање watchpoint-овима из конзоле. Ово даје већу контролу од GUI-ја: можете одредити величину области посматрања, поставити услов активирања, креирати watchpoint-ове на адресама добијеним из израчунатих израза и аутоматски извршавати радње при активирању. Интерфејс командне линије је посебно користан при отклањању грешака у сложеним сценаријима где је потребно брзо мењати параметре посматрања.
Команда watchpoint set variable прихвата име променљиве узимајући у обзир домен видљивости: за self својства у Objective-C користите self->_property, за Swift — self.property. Параметар -w write поставља праћење уписа, -w read — читања (режим доступан само на неким архитектурама), -s size — величину области у бајтовима. Након постављања watchpoint-а, листа се може видети командом 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'
Као и breakpoint-ови, watchpoint-ови подржавају условно активирање. Параметар -c поставља услов у Swift или Objective-C. На пример, watchpoint са условом newValue > 1000 зауставиће се само када уписана вредност пређе хиљаду. Ово је критично при отклањању грешака у петљама или сензорима који генеришу хиљаде промена у секунди — иначе би се дебагер заустављао при свакој промени, чинећи рад немогућим.
(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
// Аутоматски забележи вредност и настави извршење
Посебност watchpoint-ова за својства објеката Objective-C и Swift — watchpoint се поставља не на име својства, већ на адресу поља ivar (променљиве инстанце) у меморији објекта. То значи да при сваком новом алодирању објекта (на пример, при поновном креирању ViewController-а), watchpoint постаје неважећи јер се меморијска адреса променила. За стално посматрање својства кроз рестартове потребно је поново поставити watchpoint у тренутку иницијализације објекта.
Посматрање елемената низова и речника захтева израчунавање адресе конкретног елемента. На пример, за посматрање трећег елемента низа користите watchpoint set expression -- &array[2]. Ако низ поново алодира интерни бафер (при додавању елемената преко капацитета), watchpoint постаје неважећи — LLDB пријављује грешку Watchpoint 1 has an invalid address. У таквим случајевима потребно је поново поставити watchpoint након промене величине колекције.
(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
У Objective-C-у можете пратити retain count објекта постављањем watchpoint-а на поље retainCount у структури objc_object. LLDB то извршава преко watchpoint set expression -w write -- (int*)[object retainCount]. Међутим, за Swift објекте са ARC-ом (Automatic Reference Counting) retain count није директно доступан — уместо тога користите Instruments или Memory Graph Debugger за анализу цурења меморије. Према Apple-у, watchpoint-ови на retain count раде само у Debug верзијама са искљученом ARC оптимизацијом.
Watchpoint-ови имају низ ограничења која треба узети у обзир. Главно — број хардверских watchpoint-ова је ограничен на 4–8 регистара на ARM архитектури и до 4 на x86. Када су сви хардверски регистри заузети, LLDB прелази у режим софтверског watchpoint-а: мења сваку инструкцију уписа у праћеном опсегу да би генерисао изузетак. То успорава извршење 10–50 пута, због чега се у пракси препоручује коришћење не више од 2–3 активна watchpoint-а истовремено.
Друго ограничење — неважење watchpoint-ова при преалокацији меморије. Приликом рада сакупљача отпада или ARC-а, када се објекат премешта у меморији (у језицима са компактизацијом хипа), адреса watchpoint-а постаје неисправна. У Swift-у и Objective-C-у ARC не премешта објекте, али реалокација низова и стрингова доводи до истог ефекта. LLDB упозорава на ово поруком Watchpoint N address (0x...) doesn't contain a valid allocation.
Треће ограничење — видљивост променљивих. Watchpoint на локалну променљиву ради само док се та променљива налази у домену видљивости тренутног стек оквира. Чим се функција заврши, watchpoint се аутоматски брише. За посматрање глобалних променљивих или поља дуготрајних објеката, watchpoint се чува до експлицитног брисања преко watchpoint delete или до завршетка процеса.
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # Удалить все watchpoints
2 watchpoints deleted.
(lldb) watchpoint disable 1 # Временно отключить
(lldb) watchpoint enable 1 # Включить снова
Према ARM-у, watchpoint-ови на Apple Silicon (M1–M4) подржавају величину праћене области од 1 до 8 бајтова. За посматрање структура већих од 8 бајтова потребно је поставити више watchpoint-ова на свако поље. Ово је важно при отклањању грешака у сложеним структурама података, попут CGRect (16 бајтова) или UIEdgeInsets (16 бајтова).
Често постављана питања
Breakpoint је везан за линију кода — зауставља се по њеном достизању. Watchpoint је везан за меморијску адресу — зауставља се при упису на ту адресу из било ког дела програма. Watchpoint тражи „ко мења вредност“, breakpoint — „шта се дешава на овој линији“.
4–8 хардверских на ARM-у (укључујући Apple Silicon). Прекорачење пребацује watchpoint у софтверски режим, што успорава извршење 10–50 пута. Препоручује се не више од 2–3 активна watchpoint-а истовремено.
Watchpoint-ови се не чувају између сесија отклањања грешака. При сваком новом покретању меморијске адресе се мењају и watchpoint се мора поново поставити. Изузетак су watchpoint-ови на глобалне променљиве са фиксном адресом.
Да, али watchpoint се поставља на ivar (подржавајуће складиште) својства, а не на само својство. У Swift-у користите watchpoint set variable self.property — LLDB ће аутоматски пронаћи одговарајући ivar по имену својства.
Хардверски watchpoint не утиче на перформансе — прекид се дешава на нивоу процесора. Софтверски (при истрошењу хардверских регистара) успорава извршење 10–50 пута, јер LLDB проверава сваку инструкцију уписа.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође