Watchpoint (نقطة المراقبة) — آلية تصحيح توقف تنفيذ البرنامج عند تغيير قيمة متغير محدد أو منطقة ذاكرة. على عكس breakpoint المرتبط بسطر كود، فإن watchpoint يتتبع تغييرات البيانات بغض النظر عن مكان حدوث التعديل في البرنامج. وفقًا لـ Apple Developer Documentation، فإن watchpoints لا غنى عنها لتصحيح سباقات البيانات والتغييرات غير المتوقعة للخصائص وتتبع دورة حياة الكائنات.
الرئيسية
Watchpoint — هي آلية مصحح توقف تنفيذ البرنامج عندما تتغير القيمة في عنوان ذاكرة محدد. إذا كان breakpoint يتفاعل عند الوصول إلى سطر كود معين، فإن watchpoint يتفاعل عند الكتابة في خلية ذاكرة محددة — بغض النظر عن أي جزء من الكود قام بالكتابة. هذا يجعله لا غنى عنه للعثور على تعديلات غير متوقعة للمتغيرات، خاصة في التطبيقات متعددة الخيوط.
يعتمد مبدأ العمل على الدعم العتادي للمعالج. توفر شرائح ARM الحديثة (Apple Silicon، Qualcomm Snapdragon) من 4 إلى 8 سجلات watchpoint عتادية. عندما ينفذ المعالج تعليمة كتابة إلى عنوان يطابق watchpoint، يحدث مقاطعة عتادية يلتقطها المصحح. عندما تنفد السجلات العتادية، يتحول LLDB إلى الوضع البرمجي — يتحقق خطوة بخطوة من كل تعليمة كتابة، مما يبطئ التنفيذ بعشرات المرات.
وفقًا لـ ARM Architecture Reference Manual، تعمل watchpoints العتادية على مستوى وحدة Data Watchpoint and Trace (DWT) ولا تتطلب تعديل الكود القابل للتنفيذ. زمن الاستجابة هو نانوثانية، مما يسمح بتتبع حتى تغييرات المتغيرات عالية التردد في حلقات معالجة البيانات مثل مخازن الصوت أو إطارات الفيديو.
Watchpoint لا غنى عنه عندما تعرف أي متغير يتغير لكنك لا تعرف من أين. سيناريوهات نموذجية: خاصية frame لـ UIView تتغير بدون سبب واضح؛ عداد في خلفية يعيد ضبطه بشكل غير متوقع؛ علامة isLoading تتبدل قبل اكتمال طلب الشبكة. في كل هذه الحالات، وضع breakpoints في جميع الأماكن التي قد يحدث فيها كتابة غير عملي. Watchpoint يحل المشكلة بأمر واحد.
يدعم Xcode وLLDB ثلاثة أنواع من watchpoints: 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 عبر منطقة التصحيح: توقف عند breakpoint، ابحث عن المتغير المطلوب في لوحة Variables View، انقر بزر الماوس الأيمن واختر Watch Variable. سيقوم Xcode تلقائيًا بتنفيذ الأمر watchpoint set variable مع اسم المتغير الصحيح والسياق. بعد ذلك، سيتوقف المصحح عند كل تغيير قيمة — مناسب للتصحيح السريع دون التبديل إلى وحدة التحكم. ومع ذلك، تعمل هذه الطريقة فقط طالما كان المتغير في نطاق الإطار الحالي.
func processItems(_ items: [String]) {
var index = 0
// قم بتعيين watchpoint على الفهرس عبر الواجهة الرسومية:
// توقف هنا، انقر بزر الماوس الأيمن → Watch Variable
for item in items {
index += 1
print("العنصر \(index): \(item)")
}
}
LLDB يوفر مجموعة كاملة من الأوامر لإدارة watchpoints من وحدة التحكم. هذا يعطي تحكمًا أكبر من الواجهة الرسومية: يمكنك تعيين حجم منطقة المراقبة، تحديد شروط التفعيل، إنشاء watchpoints على عناوين تم الحصول عليها من تعبيرات محسوبة، وتنفيذ إجراءات تلقائيًا عند التفعيل. واجهة الأوامر مفيدة بشكل خاص عند تصحيح سيناريوهات معقدة تتطلب تغيير سريع لمعلمات المراقبة.
يأخذ الأمر 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'
مثل breakpoints، تدعم watchpoints التفعيل الشرطي. المعامل -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
// تسجيل القيمة تلقائيًا ومتابعة التنفيذ
خاصية مميزة لـ watchpoints لخصائص كائنات 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 (التعداد المرجعي التلقائي)، لا يمكن الوصول إلى retain count مباشرة — بدلاً من ذلك استخدم Instruments أو Memory Graph Debugger لتحليل التسريبات. وفقًا لـ Apple، تعمل watchpoints على retain count فقط في بنيات Debug مع تعطيل تحسين ARC.
Watchpoints لها عدة قيود مهمة يجب مراعاتها. الرئيسي — عدد watchpoints العتادية محدود بـ 4–8 سجلات على بنية ARM وحتى 4 على x86. عندما تكون جميع السجلات العتادية مشغولة، يتحول LLDB إلى وضع watchpoint البرمجي: يعدل كل تعليمة كتابة في النطاق المتتبع لتوليد استثناء. هذا يبطئ التنفيذ بمقدار 10–50 مرة، لذلك في الممارسة يوصى باستخدام لا يزيد عن 2–3 watchpoints نشطة في وقت واحد.
الحد الثاني — إبطال watchpoints عند إعادة تخصيص الذاكرة. عند تفعيل جمع القمامة أو 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 # حذف جميع نقاط المراقبة
2 watchpoints deleted.
(lldb) watchpoint disable 1 # تعطيل مؤقت
(lldb) watchpoint enable 1 # إعادة التفعيل
وفقًا لـ ARM، تدعم watchpoints على Apple Silicon (M1–M4) حجم منطقة مراقبة من 1 إلى 8 بايت. لمراقبة هياكل أكبر من 8 بايت، تحتاج إلى تعيين watchpoints متعددة على كل حقل. هذا مهم عند تصحيح هياكل بيانات معقدة مثل CGRect (16 بايت) أو UIEdgeInsets (16 بايت).
الأسئلة الشائعة
Breakpoint مرتبط بسطر كود — يتوقف عند الوصول إليه. Watchpoint مرتبط بعنوان ذاكرة — يتوقف عند الكتابة في ذلك العنوان من أي مكان في البرنامج. Watchpoint يبحث عن «من يغير القيمة»، breakpoint يبحث عن «ماذا يحدث في هذا السطر».
4–8 watchpoints عتادية على ARM (بما في ذلك Apple Silicon). تجاوز هذا يحول watchpoint إلى الوضع البرمجي، مما يبطئ التنفيذ بمقدار 10–50 مرة. يوصى باستخدام لا يزيد عن 2–3 watchpoints نشطة في المرة الواحدة.
Watchpoints لا تُحفظ بين جلسات التصحيح. مع كل تشغيل جديد، تتغير عناوين الذاكرة، ويجب إعداد watchpoint مرة أخرى. الاستثناء هو watchpoints على المتغيرات العالمية بعنوان ثابت.
نعم، لكن يتم تعيين watchpoint على ivar (التخزين الخلفي) للخاصية، وليس على الخاصية نفسها. في Swift، استخدم watchpoint set variable self.property — سيجد LLDB تلقائيًا ivar المقابل باسم الخاصية.
Watchpoints العتادية لا تؤثر على الأداء — المقاطعة تحدث على مستوى المعالج. Watchpoints البرمجية (عند استنفاد السجلات العتادية) تبطئ التنفيذ بمقدار 10–50 مرة، حيث يتحقق LLDB من كل تعليمة كتابة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا