เบรกพอยต์ (breakpoint) คือเครื่องหมายพิเศษในโค้ด เมื่อถึงจุดนี้ดีบักเกอร์จะหยุดการทำงานของโปรแกรมเพื่อตรวจสอบสถานะ ตาม Apple Debugging Guide breakpoints ช่วยให้นักพัฒนาสามารถดูค่าตัวแปร สแต็กการเรียก และดำเนินการทีละขั้นตอนโดยไม่ต้องแก้ไขซอร์สโค้ด เป็นเครื่องมือหลักในการวินิจฉัยข้อผิดพลาดและวิเคราะห์พฤติกรรมของแอปพลิเคชันแบบเรียลไทม์
ประเด็นสำคัญ
Breakpoint คือเครื่องหมายที่ใช้งานอยู่ซึ่งวางบนบรรทัดเฉพาะของซอร์สโค้ด เมื่อถึงบรรทัดนี้ ดีบักเกอร์จะบังคับหยุดการทำงานของเธรด ในขณะนี้ นักพัฒนาได้รับการควบคุมสถานะของแอปพลิเคชันอย่างสมบูรณ์: สามารถดูค่าของตัวแปรทั้งหมดในขอบเขตปัจจุบัน ตรวจสอบสแต็กการเรียก ดำเนินการนิพจน์ตามอำเภอใจ และดำเนินการต่อทีละขั้นตอน หากไม่มี breakpoints การดีบักจะลดลงเหลือเพียงการเพิ่มนิพจน์ print ชั่วคราวอย่างไม่สิ้นสุดแล้วลบออก — วิธีการที่ทำให้โค้ดสกปรกและไม่ให้การควบคุมแบบโต้ตอบ
วัตถุประสงค์หลักของ breakpoint คือการระบุแหล่งที่มาของข้อผิดพลาด เมื่อแอปพลิเคชันทำงานไม่คาดคิด นักพัฒนาจะวางจุดหยุดก่อนส่วนที่น่าสงสัยและวิเคราะห์ตามลำดับว่าข้อมูลใดเข้ามา ตัวแปรเปลี่ยนแปลงอย่างไร และการดำเนินการดำเนินไปตามเส้นทางใด ตาม Apple มากกว่า 70% ของข้อผิดพลาดในแอปมือถือถูกระบุอย่างแม่นยำโดยใช้ breakpoints ร่วมกับการดำเนินการทีละขั้นตอน แทนที่จะวิเคราะห์โค้ดแบบคงที่
Breakpoints ไม่ส่งผลกระทบต่อประสิทธิภาพของบิลด์ที่เผยแพร่ — จะถูกคอมไพล์เฉพาะในการกำหนดค่า Debug เท่านั้น Xcode มีแฟล็กพิเศษ DEBUG ที่ครอบโค้ดดีบักด้วยคำสั่งพรีโปรเซสเซอร์ เพื่อให้แน่ใจว่า breakpoints ไม่เข้าไปใน App Store และไม่ทำให้ผู้ใช้ปลายทางช้าลง
เมื่อโปรเซสเซอร์ถึงบรรทัดที่มี breakpoint จะเกิดการขัดจังหวะของฮาร์ดแวร์หรือซอฟต์แวร์ ใน Xcode จะใช้กลไก SIGTRAP — สัญญาณติดตามที่ถูกดีบักเกอร์ดักจับ LLDB ระงับเธรดทั้งหมด ส่งต่อการควบคุมไปยังอินเทอร์เฟซ Xcode และรอคำสั่งของนักพัฒนา: ดำเนินการต่อ (continue) ข้าม (step over) เข้าไป (step into) หรือออก (step out)
func fetchUserData(userId: Int) {
// LLDB will stop here if breakpoint is set
let url = URL(string: "https://api.example.com/user/\(userId)")
var request = URLRequest(url: url)
request.httpMethod = "GET"
print("Fetching user \(userId)")
}
ในตัวอย่างข้างต้น breakpoint ที่วางบนบรรทัด let url = ... ช่วยให้ตรวจสอบว่า userId ใดถูกส่งไปยังฟังก์ชัน URL ถูกประกอบอย่างถูกต้องหรือไม่ และส่วนหัวใดถูกตั้งค่าในคำขอก่อนที่จะดำเนินการเรียกเครือข่าย
Xcode มี breakpoints หลักห้าประเภท แต่ละประเภทแก้ไขงานดีบักเฉพาะ การเข้าใจความแตกต่างช่วยให้เลือกเครื่องมือที่เหมาะสมที่สุดสำหรับแต่ละสถานการณ์และลดเวลาในการวินิจฉัยลง 2–3 เท่าเมื่อเทียบกับการใช้เฉพาะจุดหยุดแบบบรรทัด
| ประเภท Breakpoint | วัตถุประสงค์ | การเปิดใช้งาน |
|---|---|---|
| Line breakpoint | หยุดที่บรรทัดโค้ดเฉพาะ | คลิกที่หมายเลขบรรทัดในตัวแก้ไข |
| Conditional breakpoint | หยุดเมื่อเงื่อนไขเป็นจริง | คลิกขวา → Edit Breakpoint → Condition |
| Symbolic breakpoint | หยุดเมื่อเรียกฟังก์ชัน/เมธอด | Breakpoint Navigator → + → Symbolic Breakpoint |
| Exception breakpoint | หยุดเมื่อมีการโยนข้อยกเว้น | Breakpoint Navigator → + → Exception Breakpoint |
| Error breakpoint | หยุดเมื่อเกิดข้อผิดพลาด (Swift) | Breakpoint Navigator → + → Swift Error Breakpoint |
Line breakpoint เป็นประเภทที่พบบ่อยที่สุด ตั้งค่าได้ด้วยคลิกเดียวที่หมายเลขบรรทัดในตัวแก้ไข Xcode เมื่อถึงบรรทัดนั้น การดำเนินการจะหยุดชั่วคราวและนักพัฒนาสามารถตรวจสอบสถานะผ่านแผง Debug Area หรือคอนโซล LLDB ตามสถิติของ Stack Overflow นักพัฒนา iOS มากกว่า 85% ใช้ breakpoints แบบบรรทัดเป็นเครื่องมือดีบักหลัก ในขณะที่ประเภทอื่นใช้สำหรับสถานการณ์เฉพาะ เช่น การดีบักไลบรารีของบุคคลที่สามหรือการดักจับข้อยกเว้น
Symbolic breakpoint ช่วยให้หยุดเมื่อเรียกเมธอดหรือฟังก์ชันเฉพาะ แม้ว่าจะไม่สามารถเข้าถึงซอร์สโค้ดของเมธอดนั้นก็ตาม สิ่งนี้มีค่าอย่างยิ่งเมื่อดีบักเฟรมเวิร์กระบบ — ตัวอย่างเช่น เพื่อดักจับช่วงเวลาที่ UIKit เรียก layoutSubviews การกำหนดค่าประกอบด้วยชื่อสัญลักษณ์ (เช่น -[UIView layoutSubviews] สำหรับ Objective-C หรือ UIView.layoutSubviews() สำหรับ Swift) และพารามิเตอร์เสริม: โมดูล เงื่อนไข และจำนวนที่ไม่สนใจ
// Symbolic breakpoint to intercept layoutSubviews on UITableView
// Symbol name: -[UITableView layoutSubviews]
// Action: po UITableView.appearance()
class CustomTableView: UITableView {
override func layoutSubviews() {
super.layoutSubviews()
// Symbolic breakpoint here will intercept the call
print("layoutSubviews called")
}
}
Breakpoint แบบมีเงื่อนไข จะไม่ทำงานทุกครั้งที่มีการดำเนินการบรรทัด แต่จะทำงานเฉพาะเมื่อนิพจน์ตรรกะที่ระบุถูกประเมินเป็น true ซึ่งประหยัดเวลาอย่างมากเมื่อดีบักลูป การประมวลผลอาร์เรย์ และการเรียกซ้ำ — แทนที่จะคลิก Continue ด้วยตนเองทุกครั้ง นักพัฒนาจะกำหนดเงื่อนไขและดีบักเกอร์จะหยุดเฉพาะในช่วงเวลาที่เกี่ยวข้อง
ในการเพิ่มเงื่อนไข ให้คลิกขวาที่ breakpoint เลือก Edit Breakpoint และป้อนนิพจน์ใน Swift หรือ Objective-C ในฟิลด์ Condition อนุญาตให้เปรียบเทียบ ตัวดำเนินการตรรกะและการเรียกเมธอดที่ไม่มีผลข้างเคียง Xcode ประเมินนิพจน์ในบริบทของโปรแกรมที่หยุดและถ้าเป็นจริง ดีบักเกอร์จะบันทึกสถานะ
for index in 0..<1000 {
// Breakpoint with condition: index == 500
// The debugger will stop only on the 501st iteration
processItem(at: index)
}
นอกเหนือจากเงื่อนไข breakpoint สามารถดำเนินการการกระทำอัตโนมัติโดยไม่หยุดโปรแกรม สิ่งนี้ดำเนินการผ่านตัวเลือก Automatically continue after evaluating ในการตั้งค่า breakpoint การดำเนินการรวมถึง: แสดงค่าในคอนโซล (po variable) เล่นสัญญาณเสียง ดำเนินการคำสั่ง LLDB ตามอำเภอใจ หรือเรียกใช้สคริปต์เชลล์ วิธีนี้แทนที่นิพจน์ print ชั่วคราวและช่วยให้บันทึกข้อมูลโดยไม่ต้องแก้ไขซอร์สโค้ด
// Breakpoint with action: po “Index: \(index), value: \(items[index])”
// Automatically continue = true → program does not stop
func processItems(_ items: [String]) {
for (index, item) in items.enumerated() {
// Here the breakpoint logs every iteration without stopping
print("Processing \(item)")
}
}
เทคนิคนี้มีประโยชน์โดยเฉพาะเมื่อดีบักการอัปเดต UI — ตัวอย่างเช่น เพื่อบันทึกการเปลี่ยนแปลงเฟรมทั้งหมดโดยไม่รบกวนโค้ดของคอนโทรลเลอร์ ตาม Ray Wenderlich การใช้การดำเนินการของ breakpoint แทนนิพจน์ print ชั่วคราวช่วยลดเวลาในการดีบักลง 30–40% เนื่องจากไม่จำเป็นต้องทำความสะอาดโค้ดในภายหลัง
แม้ว่า Xcode จะมีอินเทอร์เฟซกราฟิกที่สะดวก แต่ LLDB รองรับคำสั่งหลายสิบคำสั่งสำหรับการจัดการจุดหยุดโดยทางโปรแกรมโดยตรงจากคอนโซลของดีบักเกอร์ ซึ่งให้ความสามารถที่ไม่สามารถใช้ได้ผ่าน GUI: การปิดใช้งาน breakpoints เป็นกลุ่มด้วยนิพจน์ปกติ การตั้งค่าจุดหยุดในไลบรารีที่โหลดแบบไดนามิก และการสร้างทริกเกอร์หลายขั้นตอนที่ซับซ้อน
| คำสั่ง LLDB | คำอธิบาย | ตัวอย่าง |
|---|---|---|
| breakpoint set | ตั้งค่า breakpoint | breakpoint set -f ViewController.swift -l 42 |
| breakpoint list | แสดง breakpoints ทั้งหมด | breakpoint list |
| breakpoint disable | ปิดใช้งาน breakpoint ตามหมายเลข | breakpoint disable 1 |
| breakpoint delete | ลบ breakpoint | breakpoint delete 1.2 |
| breakpoint modify | แก้ไขเงื่อนไขหรือการดำเนินการ | breakpoint modify -c “i > 100” 1 |
(lldb) breakpoint set -f LoginViewController.swift -l 15 -c "email.isEmpty"
Breakpoint 1: 15 locations added.
(lldb) breakpoint modify 1 -C "po email" -G true
(lldb) breakpoint list
1: name = 'LoginViewController.swift:15', condition = 'email.isEmpty'
1.1: addr = 0x1000a3b40
LLDB รองรับการตั้งค่า breakpoints ด้วยนิพจน์ปกติสำหรับชื่อฟังก์ชัน ซึ่งช่วยให้ดักจับเมธอดทั้งหมดที่ตรงกับรูปแบบ — ตัวอย่างเช่น เมธอดทั้งหมดที่ขึ้นต้นด้วย handle ในคลาสเฉพาะ วิธีนี้ใช้ในระหว่างการปรับโครงสร้างและการวิเคราะห์โค้ดที่ไม่คุ้นเคยเมื่อคุณต้องการเข้าใจว่าเมธอดใดมีส่วนร่วมในการประมวลผลเหตุการณ์เฉพาะ
(lldb) breakpoint set -r "handle[A-Z]" -s DataManager
Breakpoint 2: 6 locations.
(lldb) breakpoint set -r ".*Error.*"
Breakpoint 3: 23 locations.
Exception breakpoint หยุดการทำงานของโปรแกรมเมื่อมีการโยนข้อยกเว้นใดๆ — ทั้งข้อผิดพลาด Objective-C และ Swift ใน Xcode คุณสามารถกำหนดค่าให้ดักจับเฉพาะข้อยกเว้น Objective-C เฉพาะข้อผิดพลาด Swift หรือทุกประเภท เป็นเครื่องมือที่ขาดไม่ได้เมื่อแอปพลิเคชันล่มโดยไม่มีข้อบ่งชี้ตำแหน่งในโค้ดที่ชัดเจน — ตัวอย่างเช่น เมื่อเข้าถึงออบเจกต์ที่ถูกยกเลิกการจัดสรร
Swift Error Breakpoint เป็นประเภทพิเศษที่เปิดตัวใน Xcode 11 มันดักจับช่วงเวลาที่ฟังก์ชัน Swift โยนข้อผิดพลาดผ่าน throw ก่อนที่จะไปถึงบล็อก catch ซึ่งช่วยให้เห็นว่าฟังก์ชันใดสร้างข้อผิดพลาดและด้วยอาร์กิวเมนต์ใด ซึ่งสำคัญอย่างยิ่งเมื่อดีบัก chain การเรียกที่ซับซ้อนหลายระดับของการจัดการข้อผิดพลาด
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed(String)
}
func loadUserProfile(id: Int) throws -> UserProfile {
guard id > 0 else {
throw NetworkError.invalidURL
}
// Swift Error Breakpoint will stop here on throw
return UserProfile(id: id, name: "Test")
}
Breakpoints แบบสัญลักษณ์ยังมีประสิทธิภาพเมื่อดีบัก KVO และ NotificationCenter ด้วยการตั้งค่า breakpoint ที่ observeValue(forKeyPath:of:change:context:) นักพัฒนาสามารถดักจับการแจ้งเตือน KVO ทั้งหมดในแอปพลิเคชัน ซึ่งช่วยวินิจฉัยการอัปเดต UI ที่ไม่คาดคิดหรือ race conditions ที่เกี่ยวข้องกับการสังเกตคุณสมบัติ
การใช้ breakpoints อย่างมีประสิทธิภาพนั้นไปไกลเกินกว่าการหยุดที่บรรทัด นักพัฒนาที่มีประสบการณ์รวมประเภทของจุดหยุดเข้ากับสคริปต์ LLDB โซนหยุดชั่วคราวและการส่งออกการกำหนดค่าสำหรับการดีบักที่ทำซ้ำได้ มาดูเทคนิคที่มีประโยชน์ที่สุดซึ่งได้รับการสนับสนุนจากการปฏิบัติของวิศวกร Apple และ Google
เมื่อดีบักบั๊กที่ยากต่อการจับ ให้ใช้การรวมกันของ breakpoint ที่จุดเข้าของเมธอดและ watchpoint ในการเปลี่ยนแปลงของตัวแปรสำคัญ ตั้งค่า breakpoint แบบบรรทัดก่อนการกำหนด จากนั้นสร้าง watchpoint บนตัวแปรผ่านคำสั่ง LLDB watchpoint set variable เมื่อค่าเปลี่ยน ดีบักเกอร์จะหยุดไม่ว่าการแก้ไขจะเกิดขึ้นที่ใดในโค้ด ตาม Google วิธีนี้สามารถหาแหล่งที่มาของ data race ได้ 90% ของกรณีในการดีบักเพียงครั้งเดียว
(lldb) watchpoint set variable self->_balance
Watchpoint 1: addr = 0x600000c4b80 size = 8
state = enabled type = w
watchpoint spec: 'self._balance'
(lldb) watchpoint list
1: location = 0x600000c4b80, type = write, variable = '_balance'
Xcode อนุญาตให้จัดกลุ่ม breakpoints ผ่าน Breakpoint Navigator สร้างกลุ่มแยกสำหรับแต่ละสถานการณ์ — ตัวอย่างเช่น “เข้าสู่ระบบ” “ซื้อ” “ข้อผิดพลาดเครือข่าย” เมื่อทดสอบฟังก์ชันเฉพาะ ให้เปิดใช้งานเฉพาะกลุ่มที่เกี่ยวข้อง ปิดใช้งานกลุ่มอื่น ซึ่งป้องกันการทำงานที่ผิดพลาดและเร่งการดีบักในโปรเจกต์ขนาดใหญ่ที่จำนวน breakpoints อาจเกินหลายสิบ การส่งออกกลุ่มเป็นไฟล์ช่วยให้แชร์การกำหนดค่ากับเพื่อนร่วมงานผ่านระบบควบคุมเวอร์ชัน
สำหรับสถานการณ์ที่ซับซ้อน LLDB รองรับการดำเนินการสคริปต์ Python เมื่อ breakpoint ทำงาน ในการดำเนินการของ breakpoint ให้ระบุ script import my_debug_helper; my_debug_helper.log_state() ซึ่งเปิดความเป็นไปได้ไม่จำกัด: การรวบรวมสถิติอัตโนมัติ การเปรียบเทียบสถานะระหว่างการเรียก การสร้างรายงานความครอบคลุมการดีบัก ตาม Apple LLDB Python API ถูกใช้ใน Xcode Cloud สำหรับการวิเคราะห์การล่มอัตโนมัติระหว่างการทดสอบ CI
คำถามที่พบบ่อย
Breakpoints ที่ไม่ทำงาน ไม่ส่งผลต่อประสิทธิภาพ — จะถูกคอมไพล์เฉพาะในการกำหนดค่า Debug เท่านั้น จุดหยุดที่ทำงานอยู่จะทำให้การทำงานช้าลงเนื่องจากกลไกการขัดจังหวะของฮาร์ดแวร์ แต่เฉพาะระหว่างการดีบักเท่านั้น
ได้ ผ่าน Symbolic breakpoint โดยชื่อเมธอดหรือฟังก์ชัน LLDB จะหยุดเมื่อเรียกสัญลักษณ์ แม้ว่าซอร์สโค้ดจะไม่พร้อมใช้งาน นอกจากนี้ คุณสามารถใช้ดีสแอสเซมเบลอร์ของ LLDB สำหรับการนำทางทีละขั้นตอน
Step Over ดำเนินการบรรทัดปัจจุบันทั้งหมด (รวมถึงการเรียกฟังก์ชัน) และหยุดที่บรรทัดถัดไป Step Into เข้าไปในฟังก์ชันที่ถูกเรียก เพื่อให้สามารถดีบักทีละขั้นตอน Step Out ส่งคืนควบคุมให้กับผู้เรียก
Breakpoints จะถูกบันทึกโดยอัตโนมัติใน xcuserdata ภายในโปรเจกต์ สำหรับแบ่งปันกับเพื่อนร่วมงาน ให้ใช้การส่งออกผ่าน Breakpoint Navigator → Share ไฟล์ .xcbkptlist สามารถเพิ่มไปยัง repository ได้หากการดีบักเป็นทีม
ตรวจสอบการกำหนดค่า Debug ของบิลด์ การทำงานของ breakpoint (ไอคอนสีน้ำเงิน) ความถูกต้องของสัญลักษณ์สำหรับ breakpoints แบบสัญลักษณ์ และความสอดคล้องของซอร์สโค้ดกับไบนารีที่ทำงานได้ — Clean Build Folder มักจะช่วยได้
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม