Watchpoint (نقطه مشاهده) — مکانیزم دیباگینگ است که اجرای برنامه را هنگام تغییر مقدار یک متغیر مشخص یا ناحیه حافظه متوقف میکند. برخلاف breakpoint که به یک خط کد متصل است، watchpoint تغییر داده را بدون توجه به اینکه در کدام بخش برنامه رخ داده است، ردیابی میکند. طبق Apple Developer Documentation، watchpointها برای دیباگ کردن race conditionها، تغییرات غیرمنتظره ویژگیها و ردیابی چرخه حیات اشیاء ضروری هستند.
نکات کلیدی
Watchpoint — مکانیزم دیباگر است که وقتی مقدار در آدرس حافظه مشخص شده تغییر کند، اجرای برنامه را متوقف میکند. اگر breakpoint به رسیدن به یک خط کد خاص واکنش نشان میدهد، watchpoint به نوشتن در یک خانه حافظه مشخص واکنش نشان میدهد — صرف نظر از اینکه کدام بخش کد آن نوشتن را انجام داده است. این ویژگی آن را در جستجوی تغییرات غیرمنتظره متغیرها، بهویژه در برنامههای چندنخی، ضروری میکند.
اصل کار بر پایه پشتیبانی سختافزاری پردازنده است. تراشههای مدرن ARM (Apple Silicon, Qualcomm Snapdragon) بین ۴ تا ۸ ثبات سختافزاری 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های سختافزاری به ۴–۸ ثبات در معماری ARM و تا ۴ در x86 محدود است. وقتی تمام ثباتهای سختافزاری اشغال شوند، LLDB به حالت watchpoint نرمافزاری تغییر میکند: هر دستور نوشتن در محدوده ردیابی شده را برای تولید استثنا تغییر میدهد. این کار اجرا را ۱۰–۵۰ بار کند میکند، بنابراین در عمل توصیه میشود همزمان بیش از ۲–۳ watchpoint فعال استفاده نشود.
محدودیت دوم — نامعتبر شدن watchpointها هنگام تخصیص مجدد حافظه. هنگام اجرای garbage collector یا ARC، وقتی شیء در حافظه جابجا میشود (در زبانهای با فشردهسازی heap)، آدرس 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 # حذف همه watchpointها
2 watchpoints deleted.
(lldb) watchpoint disable 1 # غیرفعال کردن موقت
(lldb) watchpoint enable 1 # فعال کردن دوباره
طبق ARM، watchpointها در Apple Silicon (M1–M4) اندازه ناحیه ردیابی شده از ۱ تا ۸ بایت را پشتیبانی میکنند. برای مشاهده ساختارهای بزرگتر از ۸ بایت باید چندین watchpoint روی هر فیلد تنظیم کرد. این در دیباگ ساختارهای داده پیچیده مانند CGRect (۱۶ بایت) یا UIEdgeInsets (۱۶ بایت) مهم است.
سوالات متداول
Breakpoint به یک خط کد متصل است — با رسیدن به آن متوقف میشود. Watchpoint به یک آدرس حافظه متصل است — با نوشتن در آن آدرس از هر جای برنامه متوقف میشود. Watchpoint به دنبال «چه کسی مقدار را تغییر میدهد» میگردد، breakpoint به دنبال «در این خط چه اتفاقی میافتد».
۴–۸ سختافزاری روی ARM (شامل Apple Silicon). تجاوز از این حد watchpoint را به حالت نرمافزاری منتقل میکند که اجرا را ۱۰–۵۰ بار کند میکند. توصیه میشود همزمان بیش از ۲–۳ watchpoint فعال نداشته باشید.
Watchpointها بین جلسات دیباگ ذخیره نمیشوند. با هر اجرای جدید آدرسهای حافظه تغییر میکنند و watchpoint باید دوباره تنظیم شود. استثنا — watchpoint روی متغیرهای سراسری با آدرس ثابت.
بله، اما watchpoint روی ivar (ذخیره پشتیبان) ویژگی تنظیم میشود، نه روی خود ویژگی. در Swift از watchpoint set variable self.property استفاده کنید — LLDB به طور خودکار ivar مربوطه را بر اساس نام ویژگی پیدا میکند.
سختافزاری watchpoint بر عملکرد تأثیر نمیگذارد — وقفه در سطح پردازنده رخ میدهد. نرمافزاری (پس از پر شدن ثباتهای سختافزاری) اجرا را ۱۰–۵۰ بار کند میکند زیرا LLDB هر دستور نوشتن را بررسی میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید