Do-Catch คือโครงสร้างภาษา Swift สำหรับการจัดการข้อผิดพลาด ซึ่งดักจับข้อยกเว้นที่ถูกโยนผ่าน throw และดำเนินการบล็อกโค้ดที่เกี่ยวข้อง แตกต่างจาก try-catch แบบดั้งเดิมในภาษาอื่น Swift ต้องการให้ฟังก์ชันที่อาจโยนข้อผิดพลาดถูกระบุอย่างชัดเจนด้วยตัวปรับ throws จากข้อมูลของ Swift.org, 2026 Do-Catch เป็นกลไกหลักในการจัดการข้อผิดพลาดใน Swift ที่รับประกันความปลอดภัยระดับชนิดข้อมูล
หัวข้อสำคัญ
Do-Catch คือโครงสร้างใน Swift ที่ประกอบด้วยบล็อก do ซึ่งภายในมีการดำเนินการโค้ดที่อาจโยนข้อผิดพลาด และบล็อก catch หนึ่งบล็อกหรือมากกว่าที่จัดการข้อผิดพลาดนั้น บล็อก do ประกอบด้วยการเรียกใช้ฟังก์ชัน throwing ที่มีคำว่า try กำกับ และบล็อก catch จะจับคู่ข้อผิดพลาดตามชนิด
Swift ใช้โมเดล การจัดการข้อผิดพลาดแบบมีโครงสร้าง ที่แตกต่างจากข้อยกเว้นใน Objective-C ที่นี่ข้อผิดพลาดไม่ใช่ exception ที่มีการคลายสแต็ก แต่เป็นค่าที่สอดคล้องกับโปรโตคอล Error การโยนข้อผิดพลาดผ่าน throw จะส่งผ่านการควบคุมไปยังบล็อก catch ที่ใกล้ที่สุดโดยไม่มีค่าใช้จ่ายในการคลายสแต็ก
คุณสมบัติสำคัญของ Swift คือ ความชัดเจนในการจัดการ คอมไพเลอร์ไม่อนุญาตให้เรียกใช้ฟังก์ชัน throwing โดยไม่มี try, do-catch หรือกลไกอื่น (try?, try!) ซึ่งช่วยขจัดสถานการณ์ที่ข้อผิดพลาดถูกมองข้าม
พื้นฐานของการจัดการข้อผิดพลาดใน Swift คือโปรโตคอล Error ชนิดใดก็ตามที่สอดคล้องกับโปรโตคอลนี้สามารถถูกโยนผ่าน throw ได้ โดยทั่วไปข้อผิดพลาดจะถูกกำหนดเป็น enum ที่ยืนยัน Error
Error คือโปรโตคอลว่าง (ตัวบ่งชี้) ไม่ต้องการการดำเนินการเมธอด เพียงแค่ยืนยันความสอดคล้องเท่านั้น คอมไพเลอร์ Swift ใช้ Error สำหรับการตรวจสอบแบบสแตติก: ฟังก์ชันที่มี throws สามารถโยนเฉพาะค่าที่สอดคล้องกับ Error
ฟังก์ชันที่มี throws ต้องถูกเรียกด้วย try หากภายในฟังก์ชันดังกล่าวมี throw เกิดขึ้น การควบคุมจะถูกส่งไปยัง do-catch ที่เรียกใช้ หากข้อผิดพลาดไม่ได้รับการจัดการในห่วงโซ่การเรียกใด ๆ ข้อผิดพลาดนั้นจะถูกส่งขึ้นไปยังระดับที่สูงขึ้น
enum FileError: Error {
case notFound
case permissionDenied
case corrupted(String)
}
func readFile(path: String) throws -> String {
guard FileManager.default.fileExists(atPath: path) else {
throw FileError.notFound
}
return try String(contentsOfFile: path)
}
rethrows คือตัวปรับสำหรับฟังก์ชันที่รับ throwing-closure เป็นพารามิเตอร์ หาก closure ที่ส่งมาไม่โยนข้อผิดพลาด การเรียกใช้ฟังก์ชัน rethrows สามารถทำได้โดยไม่ต้องมี try ซึ่งช่วยให้เขียนฟังก์ชันลำดับสูงทั่วไปที่เข้ากันได้กับทั้ง throwing และ non-throwing closures
Swift รองรับบล็อก catch หลายบล็อก พร้อมการจับคู่ตามชนิดข้อผิดพลาด คล้ายกับ when ใน Kotlin หรือ switch ตามชนิด: catch ที่ตรงกันตัวแรกจะถูกดำเนินการ ส่วนที่เหลือจะถูกข้ามไป
แต่ละบล็อก catch สามารถมีรูปแบบการจับคู่กับชนิดข้อผิดพลาดที่เฉพาะเจาะจงได้ Swift จะตรวจสอบบล็อกตามลำดับ ดังนั้นชนิดที่เฉพาะเจาะจงกว่าควรอยู่ก่อนชนิดทั่วไป หากไม่มีรูปแบบใดตรงกัน บล็อก catch ที่ไม่มีรูปแบบจะถูกดำเนินการ
ความปลอดภัยของชนิดข้อมูล ของ Do-Catch ช่วยให้จัดการข้อผิดพลาดแต่ละรายการแยกกัน หรือรวมหลายชนิดในบล็อกเดียวผ่านเครื่องหมายจุลภาคในการจับคู่รูปแบบ ซึ่งช่วยลดลูกโซ่ if-else ที่เทอะทะซึ่งเป็นลักษณะเฉพาะของ Objective-C ที่การวิเคราะห์ข้อผิดพลาดดำเนินการผ่านรหัสส่งกลับหรือ NSError พร้อมการตรวจสอบ domain และ code
Swift ยังรองรับ catch where ซึ่งเป็นการจับคู่กับเงื่อนไขเพิ่มเติม ตัวอย่างเช่น สามารถกรองข้อผิดพลาดตามรหัสหรือข้อความเฉพาะ: catch URLError where error.code == .notConnectedToInternet ซึ่งช่วยลดจำนวนบล็อก catch ในขณะที่คงความแม่นยำในการจัดการ
สำหรับการดีบักฟังก์ชัน throwing Swift มี ความแตกต่างระหว่างข้อผิดพลาดและข้อยกเว้น ข้อผิดพลาดของ Swift คือค่าส่งกลับที่ไม่ต้องคลายสแต็ก ข้อยกเว้นของ Objective-C (@try/@catch) ทำงานที่ระดับ runtime และใช้สำหรับข้อผิดพลาดร้ายแรงเท่านั้น Do-Catch จัดการกับข้อผิดพลาด ไม่ใช่ข้อยกเว้น ซึ่งทำให้คาดเดาได้และมีประสิทธิภาพ
ในการพัฒนาไลบรารีและ SDK สิ่งสำคัญคือต้องจำไว้ว่าฟังก์ชัน throwing เป็นส่วนหนึ่งของ สัญญาสาธารณะ ชนิดข้อผิดพลาดไม่ได้ระบุในลายเซ็น ดังนั้นควรบันทึกว่าฟังก์ชันสามารถโยนข้อผิดพลาดใดได้บ้างในความคิดเห็นหรือผ่าน enum คล้าย Result ซึ่งช่วยให้ผู้ใช้ API เขียนบล็อก catch ที่ถูกต้องโดยไม่ต้องดูการดำเนินการภายใน
ในบริบทของ การเขียนโปรแกรมแบบอะซิงโครนัส Do-Catch กับ async/await ช่วยแก้ปัญหา callback hell ก่อนหน้านี้ใน Swift การจัดการข้อผิดพลาดในโค้ดอะซิงโครนัสต้องใช้ closures ที่ซ้อนกันพร้อมการตรวจสอบ Error? ใน completion handler ด้วย async/await และ Do-Catch โค้ด throwing อะซิงโครนัสจะดูเหมือนโค้ดซิงโครนัส ซึ่งช่วยให้อ่านและบำรุงรักษาห่วงโซ่คำขอที่ซับซ้อนพร้อมการจัดการข้อผิดพลาดในแต่ละขั้นตอนได้ง่ายขึ้น
do {
let content = try readFile(path: "/data/config.json")
process(content)
} catch FileError.notFound {
createDefaultConfig()
} catch FileError.permissionDenied {
requestAccess()
} catch FileError.corrupted(let detail) {
logCorruption(detail)
} catch {
print("ข้อผิดพลาดที่ไม่ทราบสาเหตุ: \(error)")
}
Swift มีสามรูปแบบในการเรียกใช้ฟังก์ชัน throwing: try, try? และ try! แต่ละรูปแบบแก้ปัญหาเฉพาะและมีข้อจำกัดของตัวเอง
try ใช้ภายใน do-บล็อก และข้อผิดพลาดจะถูกจัดการใน catch นี่คือวิธีหลักในการเรียกใช้ฟังก์ชัน throwing คอมไพเลอร์ต้องการให้ try อยู่ในบริบทที่ข้อผิดพลาดสามารถถูกดักจับได้
try? แปลงผลลัพธ์ของฟังก์ชัน throwing เป็น Optional หากฟังก์ชันโยนข้อผิดพลาด try? จะส่งคืน nil มิฉะนั้นจะส่งคืน Optional พร้อมค่าที่สำเร็จ สะดวกสำหรับการเรียกที่สามารถละเว้นข้อผิดพลาดได้ แต่ต้องการตัวบ่งชี้ความสำเร็จ
try! จะระงับการจัดการข้อผิดพลาด หากฟังก์ชัน throwing โยนข้อผิดพลาด แอปพลิเคชันจะสิ้นสุดลงด้วย runtime error ใช้ try! เฉพาะเมื่อมั่นใจอย่างแน่นอนว่าข้อผิดพลาดเป็นไปไม่ได้ เช่น เมื่อโหลดทรัพยากรในตัว
// try? — ละเว้นข้อผิดพลาด ได้ nil เมื่อเกิดข้อผิดพลาด
if let data = try? Data(contentsOf: url) {
processData(data)
}
// try! — เฉพาะเมื่อมั่นใจว่าข้อผิดพลาดเป็นไปไม่ได้
let bundled = try! String(contentsOfFile: "Assets/default.txt")
// try — ตัวเลือกมาตรฐานใน do-catch
do {
let result = try performNetworkRequest()
updateUI(result)
} catch {
showError(error)
}
Do-Catch ถูกใช้อย่างแพร่หลายในการพัฒนา iOS สำหรับการทำงานกับ API ของระบบ มาดูสถานการณ์ทั่วไป: การทำงานกับระบบไฟล์ Core Data และคำขอเครือข่าย
หลายเมธอดของ FileManager เป็น throwing Do-Catch ช่วยจัดการการไม่มีไฟล์ ทรัพยากรถูกใช้งาน หรือสิทธิ์ไม่เพียงพอได้อย่างถูกต้อง
การแยกวิเคราะห์ JSON ผ่าน JSONDecoder เป็นการดำเนินการแบบ throwing Do-Catch จะดักจับข้อผิดพลาดในการถอดรหัสและข้อผิดพลาดเครือข่ายแยกกัน โดยให้ข้อความข้อผิดพลาดที่แม่นยำแก่ผู้ใช้
struct User: Codable {
let id: Int
let name: String
}
func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
// การเรียกพร้อมการจัดการ
do {
let user = try await fetchUser(id: 42)
showUser(user)
} catch let error as URLError {
showNetworkAlert(error)
} catch let error as DecodingError {
showParseError(error)
} catch {
showGenericError(error)
}
นักพัฒนา โดยเฉพาะผู้ที่ย้ายมาจากภาษาอื่น มักทำผิดพลาดลักษณะเฉพาะเมื่อใช้ Do-Catch มาดูข้อผิดพลาดที่พบบ่อยที่สุดกัน
คำถามที่พบบ่อย
ใน Swift ข้อผิดพลาดคือค่าที่สอดคล้องกับโปรโตคอล Error ไม่ใช่ exception ที่มีการคลายสแต็ก ฟังก์ชัน Throwing ต้องถูกระบุอย่างชัดเจนด้วย throws และคอมไพเลอร์ต้องการ try เมื่อเรียกใช้ ซึ่งช่วยขจัดข้อผิดพลาดที่ไม่ได้รับการจัดการในขั้นตอนการคอมไพล์
ได้ async ฟังก์ชันสามารถเป็น throws ได้เช่นกัน และ do-catch ทำงานร่วมกับมันได้ การเรียกใช้ฟังก์ชัน async throwing ต้องใช้ try await ภายใน do-บล็อก นี่คือวิธีมาตรฐานในการจัดการข้อผิดพลาดในโค้ดอะซิงโครนัสของ Swift
มีทางเลือกอื่น: try? แปลงข้อผิดพลาดเป็น nil, try! ทำให้เกิด crash เมื่อมีข้อผิดพลาด และ rethrows ช่วยส่งต่อข้อผิดพลาดจาก closure Do-catch ยังคงเป็นวิธีหลักในการจัดการอย่างชัดเจน
Swift ไม่ได้กำหนดชนิดของข้อผิดพลาดที่โยนได้ — ฟังก์ชัน throwing ใด ๆ สามารถโยนชนิดใดก็ได้ที่สอดคล้องกับ Error ให้แยกการจัดการผ่านการจับคู่ใน catch ตามชนิดข้อผิดพลาดที่เฉพาะเจาะจง
ใช่ นี่คือแนวปฏิบัติที่ดีที่สุด กำหนด AppError เป็น enum ที่ยืนยัน Error และแปลงข้อผิดพลาดของระบบเป็นข้อผิดพลาดของโดเมน ซึ่งจะรวมการจัดการและแยกระดับแอปพลิเคชันจากรายละเอียดของระบบ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม