Memory Graph هو أداة بصرية في Xcode Debug Navigator تعرض رسمًا بيانيًا للكائنات في ذاكرة التطبيق مع مراجعها المتبادلة. على عكس heap dump، يُظهر Memory Graph ليس مجرد قائمة بالكائنات، بل رسمًا بيانيًا موجهًا للمراجع حيث كل عقدة هي كائن وكل حافة هي مرجع (strong، weak، unowned). وفقًا لـ Apple WWDC 2018، تتيح الأداة اكتشاف retain cycles وتسربات الذاكرة بصريًا في ثوانٍ، دون الحاجة لتحليل البيانات الأولية لتفريغ الكومة.
الرئيسية
Memory Graph هو مكون من Xcode Debug Navigator (ظهر في Xcode 10، WWDC 2018) يقوم ببناء رسم بياني موجه لجميع الكائنات في ذاكرة العملية الجاري تصحيحها. كل عقدة في الرسم هي مثيل لفئة (Objective-C أو Swift)، وكل حافة هي مرجع لكائن آخر. يشير لون الحافة إلى نوع المرجع: أزرق — strong، أخضر — weak، رمادي — unowned. يتم بناء الرسم البياني بناءً على بيانات LLDB و Objective-C runtime، لذلك يجب تجميع التطبيق في تهيئة Debug مع تمكين الرموز لكي يعمل بشكل صحيح.
مبدأ العمل: عند إيقاف التطبيق عند breakpoint، يطلب Xcode عبر LLDB من runtime جميع الكائنات الحية ومراجعها. يستخدم LLDB objc_getClassList ويتكرر عبر مناطق التخصيص لبناء الرسم البياني الكامل. على ARM64 (Apple Silicon)، تُستخدم وسائل أجهزة إضافية لتتبع التخصيص بدون تباطؤ. يعتمد وقت بناء الرسم البياني على حجم الكومة: لتطبيق iOS نموذجي (50–200 MB)، يتم بناء الرسم البياني في 1–3 ثوانٍ.
وفقًا لـ Apple، Memory Graph هو الأداة الوحيدة التي يمكنها تصور retain cycles بدون تعديل الكود أو إضافة أدوات قياس. على عكس Instruments Leaks، يعمل Memory Graph في الوقت الفعلي داخل Xcode ولا يتطلب تشغيلًا منفصلًا للمُحلل. هذا يجعله الأداة الأولى للاختيار للتشخيص السريع لتسربات الذاكرة أثناء التطوير.
Heap dump يوفر جدولاً بجميع الكائنات مع الأرقام (shallow size، retained size) — وهو مثالي للتحليل الكمي. Memory Graph يوفر صورة بصرية للاتصالات — مثالي للعثور على المراجع الدائرية. الأدوات تكمل بعضها البعض: أولاً Memory Graph للاكتشاف السريع لـ retain cycles، ثم heap dump عبر Instruments Allocations للقياس الدقيق لـ retained size. وفقًا لـ objc.io، تغطي مجموعة الطريقتين 95% من سيناريوهات تسرب الذاكرة.
Retain cycle هو موقف حيث يحتفظ كائنان أو أكثر ببعضهما البعض بمراجع قوية، مكونين حلقة مغلقة. لا يستطيع ARC تحرير مثل هذه الحلقة لأن retain count لكل كائن لا يصل أبدًا إلى الصفر. مثال كلاسيكي: ViewController وView، حيث تمتلك View مرجعًا قويًا إلى closure يلتقط self (ViewController). يعرض Memory Graph هذه الحلقات كدوائر (حلقات)، مميزًا إياها للتحديد السريع.
عندما يكتشف Xcode retain cycle، يقوم بتمييزه بإطار برتقالي ويعرض تحذيرًا في Debug Navigator. بالنقر على الدائرة، ترى سلسلة المراجع التي تشكل الحلقة المغلقة. على المطور فقط تحديد أي حافة strong يجب أن تكون weak — عادةً هذا مرجع من كائن فرعي إلى الأصل (مثل delegate أو closure).
class ViewController: UIViewController {
let service = DataService()
override func viewDidLoad() {
super.viewDidLoad()
// ❌ Retain cycle: ViewController → service → closure → ViewController
service.fetchData { self.updateUI($0) }
}
func updateUI(_ data: Data) {}
}
class DataService {
var completion: ((Data) -> Void)?
func fetchData(handler: @escaping (Data) -> Void) {
self.completion = handler
}
}
في Memory Graph سترى مثلثًا: ViewController → DataService → closure → ViewController. الحل هو جعل التقاط self ضعيفًا: [weak self]. بعد الإصلاح، سيُظهر Memory Graph حافة خضراء من closure إلى ViewController، وسيختفي retain cycle.
// كود مصحح — الالتقاط ضعيف لـ self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
واجهة Memory Graph Debugger تتكون من ثلاث لوحات: اليسرى — قائمة بجميع الكائنات الحية (مجمعة حسب الفئة) مع عدد المثيلات؛ الوسطى — رسم بياني بصري مع عقد قابلة للسحب؛ اليمنى — مفتش للكائن أو الحافة المحددة. تعرض قائمة الكائنات: أيقونة الفئة، عدد المثيلات في الذاكرة، إجمالي retained size والنسبة المئوية من الكومة بأكملها. يدعم التصفية حسب اسم الفئة التعبيرات العادية.
يمكن سحب عقد الرسم البياني لتحسين القراءة. النقر المزدوج على عقدة يفتح معلومات مفصلة عن الكائن: جميع خصائصه مع الأنواع والقيم، مكدس الاستدعاءات (backtrace) لكل خاصية، وتاريخ retain/release. Backtrace هو ميزة رئيسية: يظهر أي سطر كود بالضبط أنشأ المرجع إلى الكائن. هذا يسمح بالعثور على مصدر التسرب دون مراجعة يدوية لكل الكود.
للرسوم البيانية المعقدة، يوفر Xcode تخطيطًا تلقائيًا عبر Layout → Hierarchical أو Cluster. يضع التخطيط الهرمي الكائنات الجذرية في الأعلى والتابعة في الأسفل، مما يبسط البحث عن السلاسل. التجميع العنقودي يجمع الكائنات ذات الصلة في عناقيد، وهو مفيد عندما يحتوي الرسم البياني على عدة مجموعات معزولة. وفقًا لـ Apple، يُوصى بالتخطيط الهرمي لمعظم التطبيقات — فهو بديهي ويستغرق وقتًا أقل للتحليل البصري.
// أوامر LLDB التي يستخدمها Memory Graph داخليًا
(lldb) script import lldb.macosx.heap
(lldb) script heap.find_variable("viewController")
0x600000c4b80: ViewController
(lldb) script heap.refs 0x600000c4b80
0x600000c4b80 -> 0x600003a4c00 (DataService)
ivar: _service, offset: 16
يتضمن النهج المنهجي لتحليل Memory Graph عدة مراحل. المرحلة 1: قم بتشغيل التطبيق، ونفذ سيناريو قد يسبب تسربًا (فتح/إغلاق شاشة، إجراء طلب شبكة). المرحلة 2: اضغط على زر Memory Graph في Debug Navigator — يبني Xcode الرسم البياني. المرحلة 3: تحقق من التحذيرات البرتقالية لـ retain cycles في اللوحة اليسرى. المرحلة 4: للكائنات المشبوهة، استخدم خيار Show only cycles — سيتم عرض العقد المشاركة في المراجع الدائرية فقط.
بمجرد العثور على retain cycle، انقر على حافة الدائرة وافتح لوحة المفتش. قسم Backtrace يظهر مكدس الاستدعاءات في اللحظة التي تم فيها إنشاء هذا المرجع. على سبيل المثال، إذا كانت الحافة تؤدي من closure إلى self، سيظهر backtrace في أي طريقة وفي أي سطر كود تم إنشاء closure. هذا يلغي الحاجة للتخمين — ترى فورًا النقطة التي تم فيها إنشاء المرجع المشكل. وفقًا لـ WWDC Labs، يقلل تحليل backtrace وقت تشخيص retain cycle من 15–20 دقيقة إلى 2–3 دقائق.
class ProfileViewController: UIViewController {
var profileView: ProfileView!
override func viewDidLoad() {
super.viewDidLoad()
profileView = ProfileView()
// سيظهر Memory Graph retain cycle هنا
profileView.onTap = { [unowned self] in
// ⚠️ unowned قد يتسبب في تعطل عند كون self فارغًا
self.navigateToDetail()
}
}
func navigateToDetail() { }
}
// ✅ صح: [weak self] + guard let self
profileView.onTap = { [weak self] in
guard let self else { return }
self.navigateToDetail()
}
يمكن لـ Memory Graph عرض آلاف الكائنات، مما يصعب البحث. استخدم عوامل التصفية في اللوحة اليسرى: أدخل اسم فئة (مثل ProfileViewController) لعرض مثيلات تلك الفئة فقط. ثم اختر مثيلاً كان يجب أن يتم تحريره (إذا كانت الشاشة مغلقة ولكن الكائن بقي). طبق Show Reachable From — سيتم عرض المراجع ذات الصلة بهذا الكائن فقط، مع إخفاء باقي الرسم البياني.
يستخدم المطورون ذوو الخبرة Memory Graph ليس فقط للعثور على التسربات، ولكن أيضًا للتحكم الاستباقي في الذاكرة. افحص Memory Graph بعد كل تغيير معماري كبير — إضافة delegate جديد، closure أو اشتراك في NotificationCenter. يكفي تنفيذ سيناريو نموذجي والتأكد من تحرير الكائنات بشكل صحيح وعدم وجود retain cycles. هذا يستغرق 2–3 دقائق لكنه يمنع ساعات من التصحيح اللاحق.
Memory Report في Xcode (علامة تبويب Debug Navigator) يعرض رسمًا بيانيًا لاستهلاك الذاكرة في الوقت الفعلي. استخدمه مع Memory Graph: افتح Memory Graph عند الارتفاع الحاد في الاستهلاك. على سبيل المثال، عند التمرير في قائمة طويلة مع خلايا تحمل صورًا، سيُظهر Memory Graph أي الكائنات يتم إنشاؤها وأيها يتم تحريرها. إذا كان عدد الكائنات ينمو بدون انخفاض — فهذا تسرب محتمل مرئي قبل أن يتسبب في تعطل. وفقًا لـ Apple، فإن مزيج Memory Graph + Memory Report هو سير العمل الموصى به لجميع مطوري iOS بدءًا من Xcode 12.
// مثال تسرب في Objective-C عبر delegation
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ يجب أن يكون weak!
@end
@implementation DownloadManager
// سيظهر Memory Graph retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end
// الإصلاح: weak property
@property (weak) id delegate;
انتبه بشكل خاص إلى closures — المصدر الأكثر شيوعًا لـ retain cycles في Swift. عند التقاط self داخل closure يتم تخزينه كخاصية لكائن، يتشكل دورة كلاسيكية. يعرض Memory Graph هذا كـ closure (عقدة برمز {}) متصلة بحواف زرقاء بالكائنات الملتقطة. افحص بانتظام جميع closures، خاصة تلك المستخدمة في الاستدعاءات غير المتزامنة، GCD، Combine و SwiftUI. وفقًا لإحصائيات Point-Free، 90% من التسربات في مشاريع Swift مرتبطة بـ closures التي تلتقط self.
الأسئلة الشائعة
يعمل Memory Graph لكلتا اللغتين لأنه يستخدم Objective-C runtime. كائنات Swift المتوافقة مع ObjC (الفئات الفرعية لـ NSObject والمعلومة بـ @objc) تُعرض بالكامل. الهياكل والفئات Swift الخالصة بدون جسر ObjC تُرى بشكل محدود.
يجب أن تكون الكائنات مسجلة في Objective-C runtime. أنواع القيم في Swift (struct، enum) لا تظهر. تأكد من أن الفئة ترث من NSObject أو تستخدم السمة @objc للظهور في Memory Graph.
أزرق — مرجع strong، يحتفظ بالكائن. أخضر — مرجع weak، لا يؤثر على دورة الحياة. رمادي — مرجع unowned. يتشكل retain cycle فقط من الحواف الزرقاء.
بناء الرسم البياني يوقف التطبيق لمدة 1–3 ثوانٍ وقد يزيد مؤقتًا استهلاك ذاكرة Xcode بمقدار 200–500 MB. التطبيق نفسه لا يتباطأ لأن الفحص يحدث أثناء توقف breakpoint.
Xcode لا يدعم تصدير الرسم البياني مباشرة. استخدم لقطة شاشة للتوثيق أو script lldb heap.find_variable لاستخراج البيانات برمجيًا. للتحليل المفصل، استخدم Instruments Allocations مع تفريغ الكومة.
الخلاصة
{}.سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا