Memory Graph: چیست، گراف اشیاء و تشخیص ارجاعات چرخه‌ای

نویسنده: IT Sectr منتشر شده: 2026-05-07 زمان مطالعه: 10 دقیقه

Memory Graph — ابزار بصری Xcode Debug Navigator است که گراف اشیاء در حافظه رم برنامه را با ارجاعات متقابلشان نمایش می‌دهد. بر خلاف heap dump، Memory Graph نه فقط لیست اشیاء، بلکه یک گراف جهت‌دار از ارجاعات را نشان می‌دهد که هر گره یک شیء و هر یال یک ارجاع (strong, weak, unowned) است. به گفته Apple WWDC 2018، این ابزار امکان تشخیص بصری retain cycles و نشت حافظه را در عرض چند ثانیه بدون نیاز به تحلیل داده‌های خام heap dump فراهم می‌کند.

نکات اصلی

  • Memory Graph — گراف بصری اشیاء در حافظه Xcode که ارجاعات بین اشیاء را در زمان واقعی نشان می‌دهد.
  • Retain cycle با یک کانتور بسته در گراف تشخیص داده می‌شود — دو یا چند شیء با ارجاعات قوی به یکدیگر اشاره می‌کنند.
  • Backtrace برای هر یال گراف نشان می‌دهد که ارجاع کجا و چه زمانی تنظیم شده است، یافتن منبع نشت را آسان‌تر می‌کند.
  • فیلتر کردن بر اساس نام کلاس و نوع ارجاع (strong/weak) امکان جداسازی سریع اشیاء مشکل‌دار را فراهم می‌کند.
  • ادغام با Memory Report در Xcode امکان ردیابی تغییر مصرف حافظه در زمان واقعی را فراهم می‌کند.

Memory Graph چیست و چگونه کار می‌کند

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) از سخت‌افزار اضافی برای ردیابی تخصیص بدون کاهش سرعت استفاده می‌شود. زمان ساخت گراف به اندازه heap بستگی دارد: برای یک برنامه iOS معمولی (50–200 MB) گراف در 1–3 ثانیه ساخته می‌شود.

به گفته Apple، Memory Graph تنها ابزاری است که می‌تواند retain cycles را بدون تغییر کد یا افزودن ابزار دقیق مصورسازی کند. بر خلاف Instruments Leaks، Memory Graph در زمان واقعی درون Xcode کار می‌کند و نیاز به راه‌اندازی جداگانه پروفایلر ندارد. این آن را به ابزار اولین انتخاب برای تشخیص سریع نشت حافظه در فرآیند توسعه تبدیل می‌کند.

تفاوت Memory Graph با heap dump

Heap dump جدولی از تمام اشیاء با اعداد (shallow size, retained size) ارائه می‌دهد — برای تحلیل کمی بهینه است. Memory Graph تصویر بصری از ارتباطات ارائه می‌دهد — برای جستجوی ارجاعات چرخه‌ای بهینه است. ابزارها مکمل یکدیگرند: ابتدا Memory Graph برای تشخیص سریع retain cycles، سپس heap dump از طریق Instruments Allocations برای اندازه‌گیری دقیق retained size. بر اساس تجربه objc.io، ترکیب دو روش 95% سناریوهای نشت حافظه را پوشش می‌دهد.

تشخیص retain cycles با Memory Graph

Retain cycle — وضعیتی که دو یا چند شیء یکدیگر را با ارجاعات قوی نگه می‌دارند و یک کانتور بسته تشکیل می‌دهند. ARC نمی‌تواند چنین کانتوری را آزاد کند، زیرا retain count هر شیء هرگز به صفر نمی‌رسد. مثال کلاسیک: ViewController و View، جایی که View یک strong reference به closure دارد که self (ViewController) را می‌گیرد. Memory Graph چنین کانتورهایی را به صورت حلقه نشان می‌دهد و برای شناسایی سریع برجسته می‌کند.

وقتی Xcode یک retain cycle را تشخیص می‌دهد، آن را با کانتور نارنجی برجسته می‌کند و در Debug Navigator اخطار نشان می‌دهد. با کلیک روی چرخه، زنجیره ارجاعات تشکیل‌دهنده کانتور بسته را می‌بینید. برای توسعه‌دهنده باقی می‌ماند که تعیین کند کدام یال قوی باید ضعیف باشد — معمولاً این ارجاع از شیء فرزند به والد است (مثلاً delegate یا closure).

swift
class ViewController: UIViewController {
    let service = DataService()

    override func viewDidLoad() {
        super.viewDidLoad()
        // ❌ چرخه retain: ViewController ← سرویس ← 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 ناپدید می‌شود.

swift
// کد اصلاح شده — گرفتن ضعیف self
service.fetchData { [weak self] data in
    guard let self else { return }
    self.updateUI(data)
}

رابط Memory Graph Debugger در Xcode

رابط Memory Graph Debugger از سه پنل تشکیل شده است: چپ — لیست تمام اشیاء زنده (گروه‌بندی شده بر اساس کلاس) با تعداد نمونه‌ها؛ مرکز — گراف بصری با گره‌های قابل کشیدن؛ راست — بازرس شیء یا یال انتخاب شده. در لیست اشیاء نمایش داده می‌شود: آیکون کلاس، تعداد نمونه‌ها در حافظه، retained size کلی و درصد از کل heap. فیلتر کردن بر اساس نام کلاس از عبارات منظم پشتیبانی می‌کند.

پیمایش در گراف

گره‌های گراف برای بهبود خوانایی قابل کشیدن هستند. کلیک دوباره روی یک گره اطلاعات دقیق درباره شیء را باز می‌کند: تمام خصوصیات آن با انواع و مقادیر، پشته فراخوانی (backtrace) برای هر خصوصیت و تاریخچه retain/release. Backtrace — ویژگی کلیدی: نشان می‌دهد کدام خط کد دقیقاً ارجاع به شیء را ایجاد کرده است. این امکان یافتن منبع نشت را بدون مرور دستی کل کد فراهم می‌کند.

برای گراف‌های پیچیده، Xcode چیدمان خودکار را از طریق Layout → Hierarchical (سلسله‌مراتبی) یا Cluster (خوشه‌ای) فراهم می‌کند. چیدمان سلسله‌مراتبی اشیاء ریشه را در بالا، اشیاء فرزند را در پایین قرار می‌دهد و جستجوی زنجیره‌ها را آسان می‌کند. چیدمان خوشه‌ای اشیاء مرتبط را در خوشه‌ها گروه‌بندی می‌کند که وقتی گراف شامل چندین گروه مجزا است مفید است. به گفته Apple، برای اکثر برنامه‌ها چیدمان سلسله‌مراتبی توصیه می‌شود — شهودی است و زمان کمتری برای تحلیل بصری می‌برد.

lldb
// دستورات 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 استفاده کنید — فقط گره‌های شرکت‌کننده در ارجاعات چرخه‌ای نمایش داده می‌شوند.

استفاده از backtrace برای یافتن منبع

وقتی retain cycle پیدا شد، روی یال چرخه کلیک کنید و پنل بازرس را باز کنید. در بخش Backtrace پشته فراخوانی در لحظه تنظیم این ارجاع نشان داده شده است. مثلاً اگر یال از closure به self منتهی می‌شود، backtrace نشان می‌دهد که closure در کدام متد و در کدام خط کد ایجاد شده است. این نیاز به حدس زدن را از بین می‌برد — بلافاصله نقطه ایجاد ارجاع مشکل‌دار را می‌بینید. به گفته WWDC Labs، تحلیل backtrace زمان تشخیص retain cycle را از 15–20 دقیقه به 2–3 دقیقه کاهش می‌دهد.

swift
class ProfileViewController: UIViewController {
    var profileView: ProfileView!

    override func viewDidLoad() {
        super.viewDidLoad()
        profileView = ProfileView()
        // Memory Graph retain cycle را اینجا نشان خواهد داد
        profileView.onTap = { [unowned self] in
            // ⚠️ unowned می‌تواند در nil self باعث crash شود
            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 نه فقط برای یافتن نشت‌ها، بلکه برای کنترل پیش‌دستانه حافظه استفاده می‌کنند. Memory Graph را بررسی کنید پس از هر تغییر عمده معماری — افزودن delegate جدید، closure یا اشتراک در NotificationCenter. کافی است یک سناریوی معمولی را اجرا کرده و مطمئن شوید که اشیاء به درستی آزاد می‌شوند و retain cycles وجود ندارند. این کار 2–3 دقیقه طول می‌کشد اما از ساعت‌ها اشکال‌زدایی بعدی جلوگیری می‌کند.

ترکیب با Memory Report

Memory Report در Xcode (زبانه Debug Navigator) نمودار مصرف حافظه را در زمان واقعی نشان می‌دهد. از آن همراه با Memory Graph استفاده کنید: هنگام افزایش ناگهانی مصرف، Memory Graph را باز کنید. مثلاً هنگام اسکرول یک لیست طولانی با سلول‌هایی که تصاویر را بار می‌کنند، Memory Graph نشان می‌دهد کدام اشیاء ایجاد می‌شوند و کدام آزاد می‌شوند. اگر تعداد اشیاء بدون کاهش افزایش می‌یابد — این یک نشت بالقوه است که قبل از منجر شدن به crash قابل مشاهده است. به گفته Apple، ترکیب Memory Graph + Memory Report workflow توصیه شده برای همه توسعه‌دهندگان iOS از Xcode 12 به بعد است.

objective-c
// مثال نشت در 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;

پروفایل کردن closureها

توجه ویژه به closures — رایج‌ترین منبع retain cycles در Swift. هنگام گرفتن self درون closure که به عنوان ویژگی یک شیء ذخیره می‌شود، یک چرخه کلاسیک تشکیل می‌شود. Memory Graph این را به عنوان یک closure (گره با نماد {}) نشان می‌دهد که با یال‌های آبی به اشیاء گرفته شده متصل است. مرتباً تمام closureها را بررسی کنید، به ویژه آنهایی که در فراخوانی‌های ناهمگام، GCD، Combine و SwiftUI استفاده می‌شوند. طبق آمار Point-Free، 90% نشت‌ها در پروژه‌های Swift مربوط به closureهایی است که self را می‌گیرند.

سوالات متداول

آیا Memory Graph فقط برای Objective-C کار می‌کند یا برای Swift هم؟

Memory Graph برای هر دو زبان کار می‌کند، زیرا از Objective-C runtime استفاده می‌کند. اشیاء Swift سازگار با ObjC (وارثان NSObject، علامت‌گذاری شده با @objc) به طور کامل نمایش داده می‌شوند. ساختارها و کلاس‌های خالص Swift بدون پل ObjC به صورت محدود قابل مشاهده هستند.

چرا Memory Graph بعضی اشیاء را نشان نمی‌دهد؟

اشیاء باید در Objective-C runtime ثبت شده باشند. Swift value types (struct, enum) نمایش داده نمی‌شوند. مطمئن شوید کلاس از NSObject ارث‌بری می‌کند یا از ویژگی @objc برای قابلیت مشاهده در Memory Graph استفاده می‌کند.

چگونه رنگ‌های یال‌ها در گراف را تفسیر کنیم؟

آبی — strong reference، شیء را نگه می‌دارد. سبز — weak reference، بر چرخه حیات تأثیر نمی‌گذارد. خاکستری — unowned reference. Retain cycle فقط از یال‌های آبی تشکیل می‌شود.

آیا Memory Graph برنامه را کند می‌کند؟

ساخت گراف برنامه را به مدت 1–3 ثانیه متوقف می‌کند و ممکن است مصرف حافظه Xcode را موقتاً 200–500 MB افزایش دهد. خود برنامه کند نمی‌شود، زیرا بازرسی در هنگام توقف روی breakpoint انجام می‌شود.

آیا می‌توان Memory Graph را برای تحلیل صادر کرد؟

Xcode از صادرات مستقیم گراف پشتیبانی نمی‌کند. برای مستندسازی از اسکرین‌شات یا از اسکریپت lldb heap.find_variable برای استخراج برنامه‌نویسی داده‌ها استفاده کنید. برای تحلیل دقیق از Instruments Allocations با heap dump استفاده کنید.

خلاصه

  • Memory Graph — ابزار بصری Xcode برای نمایش گراف اشیاء در حافظه با ارجاعاتشان.
  • Retain cycle به صورت یک کانتور بسته از یال‌های آبی (strong) نمایش داده می‌شود — Xcode آن را به رنگ نارنجی برجسته می‌کند.
  • Backtrace برای هر یال گراف مکان دقیق کد را نشان می‌دهد که ارجاع مشکل‌دار در آن ایجاد شده است.
  • فیلتر کردن بر اساس کلاس‌ها و نوع ارجاع امکان جداسازی نشت‌ها را در گراف با هزاران شیء فراهم می‌کند.
  • Closureها — منبع اصلی retain cycles در Swift، Memory Graph آنها را به صورت گره‌های {} نشان می‌دهد.
  • Weak و unowned — راه‌حل‌های شکستن چرخه، اما weak به دلیل ایمنی در nil ترجیح داده می‌شود.
  • بررسی منظم Memory Graph پس از تغییرات معماری از پسرفت حافظه در پروژه جلوگیری می‌کند.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید