defer คือโครงสร้างควบคุมการไหลใน Swift ที่กำหนดเวลาการทำงานของบล็อกโค้ดเมื่อออกจากขอบเขตปัจจุบัน บล็อก defer จะถูกทำงานไม่ว่าขอบเขตจะสิ้นสุดอย่างไร — return, break, throw, fatalError หรือการสิ้นสุดปกติ ตามคู่มือภาษา Swift (2025) เมื่อมี defer หลายตัวในขอบเขตเดียวกัน พวกมันจะทำงานในลำดับย้อนกลับของการประกาศ — defer ที่ประกาศล่าสุดจะทำงานก่อน (LIFO) สิ่งนี้ทำให้ defer ขาดไม่ได้สำหรับการล้างทรัพยากรที่รับประกัน: การปิดตัวอธิบายไฟล์ การปลดล็อก การปล่อยพอยน์เตอร์ชั่วคราวโดยไม่มีความเสี่ยงที่จะพลาดการล้างข้อมูลเมื่อออกก่อนกำหนด
ประเด็นสำคัญ
defer คือโครงสร้างควบคุมการไหลใน Swift ซึ่งเปิดตัวใน Swift 2.0 (2015) ที่เลื่อนการทำงานของบล็อกออกไปจนกว่าขอบเขตปัจจุบันจะสิ้นสุดลง คุณสมบัติหลัก: defer รับประกันว่าส่วนเนื้อหาจะทำงานไม่ว่าขอบเขตจะสิ้นสุดอย่างไร — สำเร็จ (return) มีข้อผิดพลาด (throw) ก่อนกำหนด (break, continue) หรือร้ายแรง (fatalError, precondition)
ในทางไวยากรณ์ defer เขียนเป็น defer { /* โค้ด */ } และสามารถวางไว้ที่ใดก็ได้ภายในขอบเขต คอมไพเลอร์ Swift รับประกันว่าโค้ดภายใน defer จะถูกทำงานแม้ว่าจะมีข้อยกเว้นหรือ return เกิดขึ้นระหว่างการประกาศ defer และการสิ้นสุดขอบเขต สิ่งนี้แยกความแตกต่างของ defer ออกจากโค้ดปกติที่วางไว้ท้ายฟังก์ชันซึ่งอาจถูกข้ามเมื่อออกก่อนกำหนด
ตามบทความของ Chris Lattner (ผู้สร้าง Swift, 2015) defer ได้รับแรงบันดาลใจจากโครงสร้างที่คล้ายกันในภาษาอื่น — defer ใน Go, finally ใน Java/Python, scope guard ใน C++ — แต่มีความแตกต่างที่สำคัญ: ใน Swift defer จะทำงานที่ส่วนท้ายของขอบเขต ไม่ใช่ทันทีหลังจากบล็อก try-catch สิ่งนี้ให้พฤติกรรมที่คาดเดาได้มากขึ้นสำหรับการล้างข้อมูลในฟังก์ชันที่มีจุดออกหลายจุด
ใช้ defer สำหรับการจัดการทรัพยากรแบบสมมาตร: เปิดไฟล์ → defer { close }, รับล็อก → defer { unlock } รูปแบบนี้รับประกันว่าการปล่อยทรัพยากรจะไม่มีวันพลาดไม่ว่าสถานการณ์ใด
เมื่อมีการประกาศ defer หลายตัวในขอบเขตเดียวกัน พวกมันจะทำงานใน ลำดับย้อนกลับ ของการประกาศ (LIFO — Last In, First Out) ซึ่งหมายความว่า defer ที่ประกาศล่าสุดทำงานก่อน และตัวแรกทำงานสุดท้าย:
func exampleDeferOrder() {
defer { print("defer ที่หนึ่ง") }
defer { print("defer ที่สอง") }
defer { print("defer ที่สาม") }
print("เนื้อฟังก์ชัน")
}
// ผลลัพธ์:
// เนื้อฟังก์ชัน
// defer ที่สาม
// defer ที่สอง
// defer ที่หนึ่ง
ลำดับ LIFO มีความสำคัญสำหรับการจัดการทรัพยากรที่ซ้อนกันอย่างถูกต้อง หากไฟล์ A ถูกเปิดก่อน จากนั้นไฟล์ B จะต้องปล่อยในลำดับย้อนกลับ: อันดับแรก B จากนั้น A ด้วย defer สิ่งนี้เกิดขึ้นโดยอัตโนมัติ — ประกาศ defer ทันทีหลังจากเปิดทรัพยากรแต่ละรายการ และลำดับการล้างข้อมูลจะถูกต้องไม่ว่าจำนวนจุดออกจากฟังก์ชันจะเป็นเท่าใด
ตาม Swift by Sundell (2024) คุณสมบัตินี้ทำให้ defer เหมาะอย่างยิ่งสำหรับล็อกที่ซ้อนกันและธุรกรรม: รับล็อก → defer { unlock } → รับตัวถัดไป → defer { unlock } LIFO รับประกันว่าล็อกจะถูกปล่อยในลำดับย้อนกลับของการรับ ป้องกันการดีดล็อก
กรณีการใช้งานหลักของ defer คือ การล้างทรัพยากรที่รับประกัน พิจารณาการทำงานกับระบบไฟล์ การเปิดไฟล์ผ่าน FileHandle ต้องการการปิดอย่างชัดเจน — defer รับประกันว่า close จะถูกเรียกในทุกสถานการณ์:
func readFile(path: String) throws -> String {
let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
defer { try? handle.close() }
let data = try handle.readToEnd()
guard let data else { throw FileError.empty() }
return String(data: data, encoding: .utf8) ?? ""
// handle.close() จะถูกเรียกแม้ใน throw หรือ return
}
อีกสถานการณ์ทั่วไปคือ อนิเมชัน UI พร้อมแฟล็กการโหลด ก่อนเริ่มโหลด ให้ตั้งค่าแฟล็ก isLoading = true และ defer จะตั้งค่ากลับเป็น false เมื่อออกจากฟังก์ชัน ไม่ว่าคำขอจะสำเร็จหรือล้มเหลว สิ่งนี้ป้องกันไม่ให้แฟล็กคงเป็น true เนื่องจากข้อผิดพลาดที่ไม่ถูกจัดการ ซึ่งจะบล็อกอินเทอร์เฟซตลอดไป
ตาม Bitbucket Engineering Blog (2024) defer ยังใช้สำหรับการทำโปรไฟล์: บันทึกเวลาที่จุดเริ่มต้นของฟังก์ชัน และใน defer — คำนวณและแสดงความแตกต่าง สิ่งนี้ให้การวัดประสิทธิภาพที่แม่นยำสำหรับเส้นทางการทำงานทั้งหมด รวมถึงเส้นทางที่ผิดพลาด
defer ทำงานได้อย่างมีประสิทธิภาพกับฟังก์ชัน throws เมื่อฟังก์ชันอาจโยนข้อผิดพลาดในขั้นตอนใดก็ได้ defer รับประกันการล้างข้อมูลโดยไม่ต้องทำซ้ำโค้ดในทุกบล็อก catch หรือการออกก่อนกำหนดด้วย guard:
func processTransaction() throws {
let db = try openDatabase()
defer { closeDatabase(db) }
let user = try fetchUser(from: db)
defer { logAudit(user) }
let result = try performPayment(user)
sendNotification(result)
// closeDatabase(db) และ logAudit(user) จะถูกเรียก
// เมื่อมี throw หรือ return
}
สำคัญ: defer ทำงาน ก่อน ที่การควบคุมจะถูกถ่ายโอนออกจากบล็อก catch แต่ หลัง จากที่เกิดข้อผิดพลาด หากมีการโยนข้อผิดพลาดภายใน defer Swift ไม่อนุญาตให้ใช้ try โดยตรงภายใน defer — คุณจำเป็นต้องใช้ try? หรือ try! ตามเอกสารของ Apple Swift ไม่อนุญาตให้ข้อผิดพลาดหลุดออกจาก defer เนื่องจากจะละเมิดการรับประกันการทำงานของบล็อก
วาง defer ทันทีหลังจากรับทรัพยากร สิ่งนี้เป็นไปตามหลักการความใกล้ชิด: ผู้อ่านเห็นการรับและการปล่อยอยู่ติดกัน ซึ่งช่วยเพิ่มความน่าเชื่อถือของโค้ดและทำให้การตรวจสอบโค้ดง่ายขึ้น
defer ทำงานเมื่อออกจากขอบเขตที่ประกาศไว้ หาก defer ประกาศภายในบล็อก do มันจะทำงานเมื่อออกจากบล็อกนั้น ไม่ใช่ฟังก์ชันภายนอก หากอยู่ในลูป for — ในการวนซ้ำแต่ละครั้ง:
func scopeExample() {
print("start")
do {
defer { print("defer บล็อก do") }
print("inside do")
}
// "defer บล็อก do" พิมพ์ที่นี่
print("after do")
}
// ผลลัพธ์: start, inside do, defer บล็อก do, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// ผลลัพธ์: iteration 1, end iteration 1, iteration 2, end iteration 2, ...
ตัวแปรที่ถูก defer จับจะถูกอ่าน ณ เวลาที่ออกจากขอบเขต ไม่ใช่เวลาที่ประกาศ defer หากตัวแปรเปลี่ยนแปลงระหว่างการประกาศ defer และการสิ้นสุดขอบเขต defer จะเห็นค่าล่าสุด นี่คือความแตกต่างที่สำคัญจากคลosure ซึ่งการจับจะเกิดขึ้น ณ เวลาที่สร้าง ระวัง: การเปลี่ยนแปลงตัวแปรหลังจากการประกาศ defer จะส่งผลต่อการทำงานของมัน
ข้อผิดพลาดแรก — การสันนิษฐานลำดับการทำงานอื่นนอกเหนือจาก LIFO หากลำดับการล้างข้อมูลมีความสำคัญและ defer ถูกประกาศในลำดับที่ผิด ทรัพยากรอาจถูกปล่อยโดยละเมิดการพึ่งพา วิธีแก้: ประกาศ defer ทันทีหลังจากจับทรัพยากรแต่ละรายการ ทรัพยากรที่สองถูกเปิด → defer { close second } ก่อนที่อันแรกจะถูกปิด
ข้อผิดพลาดที่สอง — การใช้ defer สำหรับตรรกะที่ไม่เกี่ยวข้องกับการล้างข้อมูล defer มีไว้สำหรับการล้างข้อมูลที่รับประกัน ไม่ใช่สำหรับการควบคุมการไหลหลัก หากโค้ดภายใน defer ส่งผลต่อค่าที่ส่งคืน มันมักจะเป็นข้อผิดพลาดเสมอ defer ไม่สามารถเปลี่ยนค่าที่ส่งคืนของฟังก์ชัน (ต่างจาก Java finally ที่ return ใน finally เขียนทับ return ดั้งเดิม)
ข้อผิดพลาดที่สาม — การโยนข้อผิดพลาดจาก defer Swift ห้าม try ภายใน defer หากข้อผิดพลาดสามารถแพร่กระจายออกไปภายนอก ใช้ try? หรือ try! สำหรับการดำเนินการที่อาจโยนข้อผิดพลาด หรือห่อไว้ในฟังก์ชันแยกต่างหากโดยไม่มี throws ตาม O’Reilly “Swift in Depth” (2025) แนวปฏิบัติที่ดีคือทำให้ฟังก์ชันล้างข้อมูลไม่โยนข้อผิดพลาด (non-throwing) หรือจัดการข้อผิดพลาดภายใน defer
คำถามที่พบบ่อย
defer คือโครงสร้าง Swift ที่เลื่อนการทำงานของบล็อกจนกว่าขอบเขตปัจจุบันจะสิ้นสุด บล็อกจะทำงานเสมอ — เมื่อ return, throw, break หรือการสิ้นสุดปกติ ใช้สำหรับการล้างทรัพยากรที่รับประกัน: ปิดไฟล์ ปลดล็อก
ในลำดับย้อนกลับของการประกาศ (LIFO) — defer ที่ประกาศล่าสุดทำงานก่อน สิ่งนี้รับประกันการล้างทรัพยากรที่ซ้อนกันอย่างถูกต้อง: หากทรัพยากร B ถูกเปิดหลัง A มันจะถูกปิดก่อน A ป้องกันการพึ่งพาทรัพยากรที่ถูกปล่อยไปแล้ว
ไม่โดยตรง — Swift ป้องกันการแพร่กระจายข้อผิดพลาดจาก defer ใช้ try? หรือ try! สำหรับการดำเนินการที่อาจโยนข้อผิดพลาด แนวปฏิบัติที่ดีที่สุดคือทำให้ฟังก์ชันล้างข้อมูลไม่โยนข้อผิดพลาดหรือจัดการข้อผิดพลาดภายใน defer โดยไม่แพร่กระจายออกไปภายนอก
defer ผูกติดกับขอบเขตและทำงานเมื่อออกใดๆ รวมถึง return, throw และ break finally (ในภาษาอื่น) ผูกติดกับ try-catch และทำงานเมื่อมี try เท่านั้น Swift ไม่มี finally — ครอบคลุมสถานการณ์นี้อย่างสมบูรณ์และทำงานสำหรับขอบเขตใดๆ ไม่ใช่แค่การจัดการข้อผิดพลาด
ใช่ defer อ่านตัวแปร ณ เวลาที่ออกจากขอบเขต ไม่ใช่เวลาที่ประกาศ หากตัวแปรเปลี่ยนแปลงหลังจากประกาศ defer บล็อก defer จะเห็นค่าล่าสุด ซึ่งแตกต่างจากคลosure ปกติที่การจับจะถูกกำหนด ณ เวลาที่สร้าง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม