Memory Graph — ابزار بصری Xcode Debug Navigator است که گراف اشیاء در حافظه رم برنامه را با ارجاعات متقابلشان نمایش میدهد. بر خلاف heap dump، Memory Graph نه فقط لیست اشیاء، بلکه یک گراف جهتدار از ارجاعات را نشان میدهد که هر گره یک شیء و هر یال یک ارجاع (strong, weak, unowned) است. به گفته Apple WWDC 2018، این ابزار امکان تشخیص بصری retain cycles و نشت حافظه را در عرض چند ثانیه بدون نیاز به تحلیل دادههای خام heap dump فراهم میکند.
نکات اصلی
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 کار میکند و نیاز به راهاندازی جداگانه پروفایلر ندارد. این آن را به ابزار اولین انتخاب برای تشخیص سریع نشت حافظه در فرآیند توسعه تبدیل میکند.
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 یک strong reference به closure دارد که self (ViewController) را میگیرد. Memory Graph چنین کانتورهایی را به صورت حلقه نشان میدهد و برای شناسایی سریع برجسته میکند.
وقتی Xcode یک retain cycle را تشخیص میدهد، آن را با کانتور نارنجی برجسته میکند و در Debug Navigator اخطار نشان میدهد. با کلیک روی چرخه، زنجیره ارجاعات تشکیلدهنده کانتور بسته را میبینید. برای توسعهدهنده باقی میماند که تعیین کند کدام یال قوی باید ضعیف باشد — معمولاً این ارجاع از شیء فرزند به والد است (مثلاً delegate یا closure).
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 ناپدید میشود.
// کد اصلاح شده — گرفتن ضعیف self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
رابط Memory Graph Debugger از سه پنل تشکیل شده است: چپ — لیست تمام اشیاء زنده (گروهبندی شده بر اساس کلاس) با تعداد نمونهها؛ مرکز — گراف بصری با گرههای قابل کشیدن؛ راست — بازرس شیء یا یال انتخاب شده. در لیست اشیاء نمایش داده میشود: آیکون کلاس، تعداد نمونهها در حافظه، retained size کلی و درصد از کل heap. فیلتر کردن بر اساس نام کلاس از عبارات منظم پشتیبانی میکند.
گرههای گراف برای بهبود خوانایی قابل کشیدن هستند. کلیک دوباره روی یک گره اطلاعات دقیق درباره شیء را باز میکند: تمام خصوصیات آن با انواع و مقادیر، پشته فراخوانی (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 میتواند در 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 را بررسی کنید پس از هر تغییر عمده معماری — افزودن delegate جدید، closure یا اشتراک در NotificationCenter. کافی است یک سناریوی معمولی را اجرا کرده و مطمئن شوید که اشیاء به درستی آزاد میشوند و retain cycles وجود ندارند. این کار 2–3 دقیقه طول میکشد اما از ساعتها اشکالزدایی بعدی جلوگیری میکند.
Memory Report در Xcode (زبانه Debug Navigator) نمودار مصرف حافظه را در زمان واقعی نشان میدهد. از آن همراه با Memory Graph استفاده کنید: هنگام افزایش ناگهانی مصرف، Memory Graph را باز کنید. مثلاً هنگام اسکرول یک لیست طولانی با سلولهایی که تصاویر را بار میکنند، Memory Graph نشان میدهد کدام اشیاء ایجاد میشوند و کدام آزاد میشوند. اگر تعداد اشیاء بدون کاهش افزایش مییابد — این یک نشت بالقوه است که قبل از منجر شدن به crash قابل مشاهده است. به گفته Apple، ترکیب Memory Graph + Memory Report workflow توصیه شده برای همه توسعهدهندگان 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 (گره با نماد {}) نشان میدهد که با یالهای آبی به اشیاء گرفته شده متصل است. مرتباً تمام closureها را بررسی کنید، به ویژه آنهایی که در فراخوانیهای ناهمگام، GCD، Combine و SwiftUI استفاده میشوند. طبق آمار Point-Free، 90% نشتها در پروژههای Swift مربوط به closureهایی است که self را میگیرند.
سوالات متداول
Memory Graph برای هر دو زبان کار میکند، زیرا از Objective-C runtime استفاده میکند. اشیاء Swift سازگار با ObjC (وارثان NSObject، علامتگذاری شده با @objc) به طور کامل نمایش داده میشوند. ساختارها و کلاسهای خالص Swift بدون پل ObjC به صورت محدود قابل مشاهده هستند.
اشیاء باید در Objective-C runtime ثبت شده باشند. Swift value types (struct, enum) نمایش داده نمیشوند. مطمئن شوید کلاس از NSObject ارثبری میکند یا از ویژگی @objc برای قابلیت مشاهده در Memory Graph استفاده میکند.
آبی — strong reference، شیء را نگه میدارد. سبز — weak reference، بر چرخه حیات تأثیر نمیگذارد. خاکستری — unowned reference. Retain cycle فقط از یالهای آبی تشکیل میشود.
ساخت گراف برنامه را به مدت 1–3 ثانیه متوقف میکند و ممکن است مصرف حافظه Xcode را موقتاً 200–500 MB افزایش دهد. خود برنامه کند نمیشود، زیرا بازرسی در هنگام توقف روی breakpoint انجام میشود.
Xcode از صادرات مستقیم گراف پشتیبانی نمیکند. برای مستندسازی از اسکرینشات یا از اسکریپت lldb heap.find_variable برای استخراج برنامهنویسی دادهها استفاده کنید. برای تحلیل دقیق از Instruments Allocations با heap dump استفاده کنید.
خلاصه
{} نشان میدهد.ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید