defer — คืออะไร ไวยากรณ์ และลำดับการทำงาน

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-20 เวลาอ่าน: 8 นาที

defer คือโครงสร้างควบคุมการไหลใน Swift ที่กำหนดเวลาการทำงานของบล็อกโค้ดเมื่อออกจากขอบเขตปัจจุบัน บล็อก defer จะถูกทำงานไม่ว่าขอบเขตจะสิ้นสุดอย่างไร — return, break, throw, fatalError หรือการสิ้นสุดปกติ ตามคู่มือภาษา Swift (2025) เมื่อมี defer หลายตัวในขอบเขตเดียวกัน พวกมันจะทำงานในลำดับย้อนกลับของการประกาศ — defer ที่ประกาศล่าสุดจะทำงานก่อน (LIFO) สิ่งนี้ทำให้ defer ขาดไม่ได้สำหรับการล้างทรัพยากรที่รับประกัน: การปิดตัวอธิบายไฟล์ การปลดล็อก การปล่อยพอยน์เตอร์ชั่วคราวโดยไม่มีความเสี่ยงที่จะพลาดการล้างข้อมูลเมื่อออกก่อนกำหนด

ประเด็นสำคัญ

  • defer — บล็อกโค้ดที่ทำงานเมื่อออกจากขอบเขต โดยไม่ขึ้นกับสาเหตุการออก (return, throw, break)
  • ลำดับ LIFO: defer หลายตัวทำงานจากล่างขึ้นบน — ตัวที่ประกาศล่าสุดทำงานก่อน
  • การล้างทรัพยากร — กรณีการใช้งานหลัก: ปิดไฟล์ ปลดล็อก ทำให้อนิเมชันเสร็จสมบูรณ์
  • การประเมินตัวแปร: defer เห็นตัวแปร ณ เวลาที่ออก ไม่ใช่เวลาที่ประกาศ
  • ไม่แทนที่ do-catch สำหรับการจัดการข้อผิดพลาด — defer รับผิดชอบการล้างข้อมูล ไม่ใช่การควบคุมการไหล

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 หลายตัว

เมื่อมีการประกาศ defer หลายตัวในขอบเขตเดียวกัน พวกมันจะทำงานใน ลำดับย้อนกลับ ของการประกาศ (LIFO — Last In, First Out) ซึ่งหมายความว่า defer ที่ประกาศล่าสุดทำงานก่อน และตัวแรกทำงานสุดท้าย:

swift
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 สำหรับการล้างทรัพยากร

กรณีการใช้งานหลักของ defer คือ การล้างทรัพยากรที่รับประกัน พิจารณาการทำงานกับระบบไฟล์ การเปิดไฟล์ผ่าน FileHandle ต้องการการปิดอย่างชัดเจน — defer รับประกันว่า close จะถูกเรียกในทุกสถานการณ์:

swift
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 และการจัดการข้อผิดพลาด

defer ทำงานได้อย่างมีประสิทธิภาพกับฟังก์ชัน throws เมื่อฟังก์ชันอาจโยนข้อผิดพลาดในขั้นตอนใดก็ได้ defer รับประกันการล้างข้อมูลโดยไม่ต้องทำซ้ำโค้ดในทุกบล็อก catch หรือการออกก่อนกำหนดด้วย guard:

swift
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 — ในการวนซ้ำแต่ละครั้ง:

swift
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 จะส่งผลต่อการทำงานของมัน

ข้อผิดพลาดทั่วไปกับ 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 คืออะไร?

defer คือโครงสร้าง Swift ที่เลื่อนการทำงานของบล็อกจนกว่าขอบเขตปัจจุบันจะสิ้นสุด บล็อกจะทำงานเสมอ — เมื่อ return, throw, break หรือการสิ้นสุดปกติ ใช้สำหรับการล้างทรัพยากรที่รับประกัน: ปิดไฟล์ ปลดล็อก

defer หลายตัวทำงานในลำดับใด?

ในลำดับย้อนกลับของการประกาศ (LIFO) — defer ที่ประกาศล่าสุดทำงานก่อน สิ่งนี้รับประกันการล้างทรัพยากรที่ซ้อนกันอย่างถูกต้อง: หากทรัพยากร B ถูกเปิดหลัง A มันจะถูกปิดก่อน A ป้องกันการพึ่งพาทรัพยากรที่ถูกปล่อยไปแล้ว

สามารถโยนข้อผิดพลาดจาก defer ได้หรือไม่?

ไม่โดยตรง — Swift ป้องกันการแพร่กระจายข้อผิดพลาดจาก defer ใช้ try? หรือ try! สำหรับการดำเนินการที่อาจโยนข้อผิดพลาด แนวปฏิบัติที่ดีที่สุดคือทำให้ฟังก์ชันล้างข้อมูลไม่โยนข้อผิดพลาดหรือจัดการข้อผิดพลาดภายใน defer โดยไม่แพร่กระจายออกไปภายนอก

ความแตกต่างระหว่าง defer และ do-catch-finally คืออะไร?

defer ผูกติดกับขอบเขตและทำงานเมื่อออกใดๆ รวมถึง return, throw และ break finally (ในภาษาอื่น) ผูกติดกับ try-catch และทำงานเมื่อมี try เท่านั้น Swift ไม่มี finally — ครอบคลุมสถานการณ์นี้อย่างสมบูรณ์และทำงานสำหรับขอบเขตใดๆ ไม่ใช่แค่การจัดการข้อผิดพลาด

defer เห็นการเปลี่ยนแปลงของตัวแปรหลังจากประกาศหรือไม่?

ใช่ defer อ่านตัวแปร ณ เวลาที่ออกจากขอบเขต ไม่ใช่เวลาที่ประกาศ หากตัวแปรเปลี่ยนแปลงหลังจากประกาศ defer บล็อก defer จะเห็นค่าล่าสุด ซึ่งแตกต่างจากคลosure ปกติที่การจับจะถูกกำหนด ณ เวลาที่สร้าง

สรุป

  • defer — บล็อกการสิ้นสุดที่ทำงานเมื่อออกจากขอบเขตไม่ว่าเหตุผลใด (return, throw, break, การสิ้นสุดปกติ)
  • ลำดับ LIFO — defer หลายตัวทำงานจากล่างขึ้นบน ตัวที่ประกาศล่าสุดทำงานก่อน
  • การล้างทรัพยากร — กรณีการใช้งานหลัก: ปิดไฟล์ ปลดล็อก ปล่อยพอยน์เตอร์ หยุดอนิเมชัน
  • ความเข้ากันได้ กับ throws: defer ทำงานหลังจากข้อผิดพลาดแต่ก่อนออกจากบล็อก catch; ข้อผิดพลาดจาก defer ไม่แพร่กระจาย
  • ขอบเขต: defer ทำงานเมื่อออกจากขอบเขตที่ประกาศ — บล็อก do, ลูป, ฟังก์ชัน
  • การจับตัวแปร: defer อ่านค่า ณ เวลาที่ออก ไม่ใช่เวลาที่ประกาศ — ระวังการเปลี่ยนแปลง
  • แนวปฏิบัติที่ดีที่สุด: ประกาศ defer ทันทีหลังจากรับทรัพยากร อย่าใช้ defer สำหรับตรรกะทางธุรกิจ ทำให้ฟังก์ชันล้างข้อมูลไม่โยนข้อผิดพลาด

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

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

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

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