Memory Graph là công cụ trực quan của Xcode Debug Navigator, hiển thị đồ thị các đối tượng trong bộ nhớ ứng dụng cùng với các tham chiếu lẫn nhau của chúng. Không giống như heap dump, Memory Graph không chỉ hiển thị danh sách đối tượng mà còn hiển thị đồ thị tham chiếu có hướng, trong đó mỗi nút là một đối tượng và mỗi cạnh là một tham chiếu (strong, weak, unowned). Theo Apple WWDC 2018, công cụ này cho phép phát hiện trực quan retain cycles và rò rỉ bộ nhớ trong vài giây, mà không cần phân tích dữ liệu thô của heap dump.
Những điểm chính
Memory Graph là một thành phần của Xcode Debug Navigator (được giới thiệu trong Xcode 10, WWDC 2018) xây dựng đồ thị có hướng của tất cả các đối tượng trong bộ nhớ của tiến trình đang gỡ lỗi. Mỗi nút của đồ thị là một thể hiện của lớp (Objective-C hoặc Swift), mỗi cạnh là một tham chiếu đến đối tượng khác. Màu của cạnh cho biết loại tham chiếu: xanh dương — strong, xanh lá — weak, xám — unowned. Đồ thị được xây dựng dựa trên dữ liệu LLDB và Objective-C runtime, do đó ứng dụng phải được biên dịch ở cấu hình Debug với các ký hiệu được bật để hoạt động chính xác.
Nguyên lý hoạt động: khi ứng dụng bị tạm dừng tại breakpoint, Xcode yêu cầu runtime thông qua LLDB tất cả các đối tượng còn sống và tham chiếu của chúng. LLDB sử dụng objc_getClassList và lặp qua các vùng cấp phát để xây dựng đồ thị hoàn chỉnh. Trên ARM64 (Apple Silicon), các phương tiện phần cứng bổ sung được sử dụng để theo dõi cấp phát mà không làm chậm. Thời gian xây dựng đồ thị phụ thuộc vào kích thước heap: đối với ứng dụng iOS điển hình (50–200 MB), đồ thị được xây dựng trong 1–3 giây.
Theo Apple, Memory Graph là công cụ duy nhất có thể trực quan hóa retain cycles mà không cần sửa đổi mã hoặc thêm công cụ đo lường. Không giống như Instruments Leaks, Memory Graph hoạt động theo thời gian thực trong Xcode và không yêu cầu khởi chạy trình phân tích riêng. Điều này khiến nó trở thành công cụ đầu tiên được lựa chọn để chẩn đoán nhanh rò rỉ bộ nhớ trong quá trình phát triển.
Heap dump cung cấp bảng tất cả các đối tượng với các con số (shallow size, retained size) — tối ưu cho phân tích định lượng. Memory Graph cung cấp hình ảnh trực quan về các kết nối — tối ưu cho việc tìm tham chiếu vòng. Hai công cụ bổ trợ cho nhau: đầu tiên Memory Graph để phát hiện nhanh retain cycles, sau đó heap dump qua Instruments Allocations để đo retained size chính xác. Theo objc.io, sự kết hợp của cả hai phương pháp bao phủ 95% các kịch bản rò rỉ bộ nhớ.
Retain cycle là tình huống khi hai hoặc nhiều đối tượng giữ nhau bằng tham chiếu strong, tạo thành một vòng lặp khép kín. ARC không thể giải phóng vòng lặp như vậy vì retain count của mỗi đối tượng không bao giờ về không. Một ví dụ cổ điển: ViewController và View, nơi View có tham chiếu strong đến một closure capture self (ViewController). Memory Graph hiển thị các vòng lặp như vậy dưới dạng các vòng (chu kỳ), làm nổi bật chúng để nhận dạng nhanh.
Khi Xcode phát hiện retain cycle, nó làm nổi bật bằng đường viền màu cam và hiển thị cảnh báo trong Debug Navigator. Nhấp vào chu kỳ sẽ hiển thị chuỗi tham chiếu tạo thành vòng lặp khép kín. Nhà phát triển chỉ cần xác định cạnh strong nào nên là weak — thường là tham chiếu từ đối tượng con đến đối tượng cha (ví dụ: delegate hoặc 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
}
}
Trong Memory Graph, bạn sẽ thấy một hình tam giác: ViewController → DataService → closure → ViewController. Giải pháp là làm cho việc capture self trở nên yếu: [weak self]. Sau khi sửa, Memory Graph sẽ hiển thị cạnh màu xanh lá từ closure đến ViewController và retain cycle sẽ biến mất.
// Mã đã sửa — capture yếu self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
Giao diện của Memory Graph Debugger bao gồm ba bảng: bên trái — danh sách tất cả các đối tượng còn sống (được nhóm theo lớp) với số lượng thể hiện; ở giữa — đồ thị trực quan với các nút có thể kéo; bên phải — trình kiểm tra cho đối tượng hoặc cạnh được chọn. Danh sách đối tượng hiển thị: biểu tượng lớp, số lượng thể hiện trong bộ nhớ, tổng retained size và phần trăm của toàn bộ heap. Lọc theo tên lớp hỗ trợ biểu thức chính quy.
Các nút của đồ thị có thể được kéo để cải thiện khả năng đọc. Nhấp đúp vào một nút sẽ mở thông tin chi tiết về đối tượng: tất cả các thuộc tính với kiểu và giá trị, ngăn xếp cuộc gọi (backtrace) cho mỗi thuộc tính và lịch sử retain/release. Backtrace là tính năng chính: nó hiển thị chính xác dòng mã nào đã thiết lập tham chiếu đến đối tượng. Điều này cho phép tìm nguồn rò rỉ mà không cần xem xét thủ công toàn bộ mã.
Đối với đồ thị phức tạp, Xcode cung cấp bố cục tự động qua Layout → Hierarchical hoặc Cluster. Bố cục phân cấp đặt các đối tượng gốc ở trên cùng và các đối tượng con ở dưới, đơn giản hóa việc tìm kiếm chuỗi. Phân cụm nhóm các đối tượng liên quan thành các cụm, thuận tiện khi đồ thị chứa nhiều nhóm biệt lập. Theo Apple, đối với hầu hết các ứng dụng, bố cục phân cấp được khuyến nghị — trực quan và mất ít thời gian phân tích hơn.
// Lệnh LLDB được Memory Graph sử dụng nội bộ
(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
Một cách tiếp cận có hệ thống để phân tích Memory Graph bao gồm nhiều bước. Bước 1: chạy ứng dụng, thực hiện kịch bản có khả năng gây rò rỉ (mở/đóng màn hình, thực hiện yêu cầu mạng). Bước 2: nhấn nút Memory Graph trong Debug Navigator — Xcode xây dựng đồ thị. Bước 3: kiểm tra cảnh báo retain cycle màu cam trong bảng bên trái. Bước 4: đối với các đối tượng nghi ngờ, sử dụng tùy chọn Show only cycles — chỉ các nút tham gia vào tham chiếu vòng mới được hiển thị.
Khi tìm thấy retain cycle, nhấp vào cạnh của chu kỳ và mở bảng kiểm tra. Phần Backtrace hiển thị ngăn xếp cuộc gọi tại thời điểm tham chiếu này được thiết lập. Ví dụ: nếu cạnh dẫn từ closure đến self, backtrace sẽ hiển thị phương thức nào và dòng mã nào đã tạo closure. Điều này loại bỏ nhu cầu phải suy đoán — bạn thấy ngay điểm mà tham chiếu có vấn đề được tạo ra. Theo WWDC Labs, phân tích backtrace giảm thời gian chẩn đoán retain cycle từ 15–20 phút xuống còn 2–3 phút.
class ProfileViewController: UIViewController {
var profileView: ProfileView!
override func viewDidLoad() {
super.viewDidLoad()
profileView = ProfileView()
// Memory Graph sẽ hiển thị retain cycle ở đây
profileView.onTap = { [unowned self] in
// ⚠️ unowned có thể gây crash khi self là nil
self.navigateToDetail()
}
}
func navigateToDetail() { }
}
// ✅ Đúng: [weak self] + guard let self
profileView.onTap = { [weak self] in
guard let self else { return }
self.navigateToDetail()
}
Memory Graph có thể hiển thị hàng nghìn đối tượng, gây khó khăn cho việc tìm kiếm. Sử dụng bộ lọc trong bảng bên trái: nhập tên lớp (ví dụ: ProfileViewController) để chỉ hiển thị các thể hiện của lớp đó. Sau đó chọn một thể hiện đáng lẽ đã được giải phóng (nếu màn hình đã đóng nhưng đối tượng vẫn còn). Áp dụng Show Reachable From — chỉ các tham chiếu liên quan đến đối tượng này mới được hiển thị, ẩn phần còn lại của đồ thị.
Các nhà phát triển giàu kinh nghiệm sử dụng Memory Graph không chỉ để tìm rò rỉ mà còn để kiểm soát bộ nhớ chủ động. Kiểm tra Memory Graph sau mỗi thay đổi kiến trúc lớn — thêm delegate, closure hoặc đăng ký NotificationCenter mới. Chỉ cần chạy một kịch bản điển hình và đảm bảo các đối tượng được giải phóng chính xác và không có retain cycles. Việc này mất 2–3 phút nhưng ngăn ngừa hàng giờ gỡ lỗi sau đó.
Memory Report trong Xcode (tab Debug Navigator) hiển thị biểu đồ mức tiêu thụ bộ nhớ theo thời gian thực. Sử dụng nó cùng với Memory Graph: mở Memory Graph khi mức tiêu thụ tăng đột biến. Ví dụ: khi cuộn danh sách dài với các ô tải hình ảnh, Memory Graph sẽ hiển thị đối tượng nào được tạo và đối tượng nào được giải phóng. Nếu số lượng đối tượng tăng mà không giảm — đó là rò rỉ tiềm ẩn có thể thấy trước khi gây ra sự cố. Theo Apple, sự kết hợp Memory Graph + Memory Report là quy trình làm việc được khuyến nghị cho tất cả nhà phát triển iOS từ Xcode 12.
// Ví dụ rò rỉ trong Objective-C qua delegation
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ Phải là weak!
@end
@implementation DownloadManager
// Memory Graph sẽ hiển thị retain cycle:
// ViewController → DownloadManager.delegate → ViewController
@end
// Sửa: weak property
@property (weak) id delegate;
Đặc biệt chú ý đến closures — nguồn phổ biến nhất của retain cycles trong Swift. Khi capture self bên trong closure được lưu trữ như một thuộc tính của đối tượng, một chu kỳ cổ điển được hình thành. Memory Graph hiển thị điều này dưới dạng closure (nút có ký hiệu {}) được kết nối bằng các cạnh màu xanh dương đến các đối tượng bị capture. Thường xuyên kiểm tra tất cả các closures, đặc biệt là những closures được sử dụng trong lời gọi bất đồng bộ, GCD, Combine và SwiftUI. Theo thống kê của Point-Free, 90% rò rỉ trong các dự án Swift liên quan đến closures capture self.
Câu hỏi thường gặp
Memory Graph hoạt động cho cả hai ngôn ngữ vì nó sử dụng Objective-C runtime. Các đối tượng Swift tương thích với ObjC (lớp con của NSObject được đánh dấu @objc) được hiển thị đầy đủ. Các cấu trúc và lớp Swift thuần túy không có cầu nối ObjC được hiển thị có giới hạn.
Các đối tượng phải được đăng ký trong Objective-C runtime. Kiểu giá trị Swift (struct, enum) không được hiển thị. Đảm bảo rằng lớp kế thừa từ NSObject hoặc sử dụng thuộc tính @objc để hiển thị trong Memory Graph.
Xanh dương — tham chiếu strong, giữ đối tượng. Xanh lá — tham chiếu weak, không ảnh hưởng đến vòng đời. Xám — tham chiếu unowned. Retain cycle chỉ được hình thành từ các cạnh xanh dương.
Việc xây dựng đồ thị tạm dừng ứng dụng trong 1–3 giây và có thể tăng tạm thời mức tiêu thụ bộ nhớ Xcode thêm 200–500 MB. Bản thân ứng dụng không bị chậm vì việc kiểm tra diễn ra trong thời gian tạm dừng breakpoint.
Xcode không hỗ trợ xuất trực tiếp đồ thị. Sử dụng ảnh chụp màn hình để tài liệu hóa hoặc script lldb heap.find_variable để trích xuất dữ liệu lập trình. Để phân tích chi tiết, sử dụng Instruments Allocations với heap dump.
Tổng kết
{}.Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm