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 ถูกตรวจพบโดยลูปปิดในกราฟ — อ็อบเจ็กต์สองตัวขึ้นไปอ้างอิงซึ่งกันและกันด้วยการอ้างอิงแบบ strong
  • 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 จะขออ็อบเจ็กต์ที่ยังมีชีวิตทั้งหมดและการอ้างอิงจาก runtime ผ่าน LLDB LLDB ใช้ objc_getClassList และวนซ้ำผ่านพื้นที่จัดสรรเพื่อสร้างกราฟที่สมบูรณ์ บน ARM64 (Apple Silicon) มีการใช้ฮาร์ดแวร์เพิ่มเติมเพื่อติดตามการจัดสรรโดยไม่ทำให้ช้าลง เวลาในการสร้างกราฟขึ้นอยู่กับขนาดฮีป: สำหรับแอปพลิเคชัน 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 คือสถานการณ์ที่อ็อบเจ็กต์สองตัวขึ้นไปยึดถือซึ่งกันและกันด้วยการอ้างอิงแบบ strong ทำให้เกิดลูปปิด ARC ไม่สามารถคืนหน่วยความจำของลูปดังกล่าวได้เพราะ retain count ของแต่ละอ็อบเจ็กต์ไม่ถึงศูนย์ ตัวอย่างคลาสสิก: ViewController และ View โดย View มีการอ้างอิงแบบ strong ไปยัง closure ที่จับ self (ViewController) Memory Graph แสดงลูปดังกล่าวเป็นวงแหวน (ไซเคิล) โดยเน้นให้เห็นเพื่อการระบุอย่างรวดเร็ว

เมื่อ Xcode ตรวจพบ retain cycle มันจะเน้นด้วยเส้นขอบสีส้มและแสดงคำเตือนใน Debug Navigator การคลิกที่ไซเคิลจะแสดงห่วงโซ่การอ้างอิงที่ก่อให้เกิดลูปปิด นักพัฒนาต้องกำหนดว่าขอบ strong ใดควรเป็น weak — โดยปกติคือการอ้างอิงจากอ็อบเจ็กต์ลูกไปยังอ็อบเจ็กต์แม่ (เช่น delegate หรือ closure)

swift
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 จะหายไป

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 ทั้งหมด และเปอร์เซ็นต์ของฮีปทั้งหมด การกรองตามชื่อคลาสรองรับนิพจน์ปกติ

การนำทางในกราฟ

โหนดของกราฟสามารถลากเพื่อปรับปรุงการอ่านได้ การดับเบิลคลิกที่โหนดจะเปิดข้อมูลโดยละเอียดเกี่ยวกับอ็อบเจ็กต์: คุณสมบัติทั้งหมดพร้อมประเภทและค่า สแต็กการเรียก (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 cycle สีส้มในแผงด้านซ้าย ขั้นตอนที่ 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 อาจทำให้ crash เมื่อ self เป็น nil
            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 จะแสดงว่าอ็อบเจ็กต์ใดถูกสร้างขึ้นและอ็อบเจ็กต์ใดถูกคืนหน่วยความจำ ถ้าจำนวนอ็อบเจ็กต์เพิ่มขึ้นโดยไม่ลดลง — นี่คือการรั่วไหลที่อาจเกิดขึ้นซึ่งมองเห็นได้ก่อนที่จะทำให้เกิดการขัดข้อง ตามข้อมูลของ Apple การรวม Memory Graph + Memory Report คือเวิร์กโฟลว์ที่แนะนำสำหรับนักพัฒนา 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;

การทำโปรไฟล์ closures

ให้ความสนใจเป็นพิเศษกับ closures — แหล่งที่มาที่พบบ่อยที่สุดของ retain cycles ใน Swift เมื่อจับ self ภายใน closure ที่ถูกเก็บเป็นคุณสมบัติของอ็อบเจ็กต์ จะเกิดไซเคิลแบบคลาสสิก Memory Graph แสดงสิ่งนี้เป็น closure (โหนดที่มีสัญลักษณ์ {}) เชื่อมต่อด้วยขอบสีน้ำเงินไปยังอ็อบเจ็กต์ที่ถูกจับ ตรวจสอบ closures ทั้งหมดเป็นประจำ โดยเฉพาะที่ใช้ในการเรียกแบบอะซิงโครนัส GCD Combine และ SwiftUI ตามสถิติของ Point-Free 90% ของการรั่วไหลในโปรเจ็กต์ Swift เกี่ยวข้องกับ closures ที่จับ self

คำถามที่พบบ่อย

Memory Graph ทำงานเฉพาะ Objective-C หรือใช้กับ Swift ได้ด้วย?

Memory Graph ทำงานได้ทั้งสองภาษาเพราะใช้ Objective-C runtime อ็อบเจ็กต์ Swift ที่เข้ากันได้กับ ObjC (คลาสย่อย NSObject ที่มี @objc) จะแสดงได้อย่างสมบูรณ์ โครงสร้างและคลาส Swift แท้ๆ ที่ไม่มีสะพาน ObjC จะแสดงอย่างจำกัด

ทำไม Memory Graph ไม่แสดงอ็อบเจ็กต์บางตัว?

อ็อบเจ็กต์ต้องถูกลงทะเบียนใน Objective-C runtime ชนิดค่า Swift (struct, enum) จะไม่แสดง ตรวจสอบให้แน่ใจว่าคลาสสืบทอดจาก NSObject หรือใช้แอตทริบิวต์ @objc เพื่อให้มองเห็นใน Memory Graph

จะตีความสีของขอบในกราฟอย่างไร?

น้ำเงิน — การอ้างอิงแบบ strong คงอ็อบเจ็กต์ไว้ เขียว — การอ้างอิงแบบ weak ไม่ส่งผลต่อวงจรชีวิต เทา — การอ้างอิงแบบ unowned 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 สำหรับแต่ละขอบของกราฟแสดงตำแหน่งที่แน่นอนในโค้ดที่สร้างการอ้างอิงที่มีปัญหา
  • การกรอง ตามคลาสและประเภทการอ้างอิงช่วยแยกการรั่วไหลในกราฟที่มีอ็อบเจ็กต์นับพัน
  • Closures เป็นแหล่งหลักของ retain cycles ใน Swift Memory Graph แสดงเป็นโหนด {}
  • Weak และ unowned เป็นวิธีแก้ไขเพื่อทำลายไซเคิล แต่ weak ดีกว่าเพราะปลอดภัยเมื่อเป็น nil
  • การตรวจสอบ Memory Graph เป็นประจำหลังการเปลี่ยนแปลงสถาปัตยกรรมป้องกันการลดลงของหน่วยความจำในโปรเจ็กต์

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม