Do-Catch: nó là gì, cấu trúc và xử lý lỗi trong Swift

Tác giả: IT Sectr Đã đăng: 2026-05-26 Thời gian đọc: 8 phút

Do-Catch là một cấu trúc của ngôn ngữ Swift để xử lý lỗi, bắt các ngoại lệ được ném ra qua throw và thực thi khối mã tương ứng. Không giống try-catch truyền thống trong các ngôn ngữ khác, Swift yêu cầu đánh dấu rõ ràng các hàm có thể ném lỗi với bộ sửa đổi throws. Theo Swift.org, 2026, Do-Catch là cơ chế xử lý lỗi chính trong Swift, cung cấp an toàn kiểu.

Các điểm chính

  • Do-Catch là một cấu trúc Swift bắt lỗi từ các khối có try và throw.
  • throws là một đánh dấu hàm cho biết nó có thể ném lỗi.
  • try là từ khóa để gọi hàm throwing bên trong khối do.
  • catch là khối xử lý lỗi với so khớp mẫu theo kiểu.
  • try?try! là các dạng thay thế chuyển đổi lỗi thành nil hoặc gây ra sự cố.

Do-Catch là gì?

Do-Catch là một cấu trúc trong Swift bao gồm một khối do, bên trong đó mã có khả năng ném lỗi được thực thi, và một hoặc nhiều khối catch xử lý lỗi đó. Khối do chứa các lời gọi đến các hàm throwing được đánh dấu bằng try, và các khối catch so khớp lỗi theo kiểu.

Swift thực hiện một mô hình xử lý lỗi có cấu trúc, khác với các ngoại lệ trong Objective-C. Ở đây, lỗi không phải là một ngoại lệ với việc mở xếp ngăn xếp, mà là một giá trị tuân thủ giao thức Error. Ném lỗi qua throw chuyển quyền điều khiển đến khối catch gần nhất mà không có chi phí mở xếp ngăn xếp.

Một tính năng chính của Swift là xử lý rõ ràng. Trình biên dịch không cho phép gọi hàm throwing mà không có try, do-catch hoặc cơ chế thay thế (try?, try!). Điều này loại bỏ các tình huống mà lỗi không được chú ý.

Mô hình lỗi trong Swift: Giao thức Error và các hàm throwing

Trung tâm của việc xử lý lỗi trong Swift là giao thức Error. Bất kỳ kiểu nào tuân thủ giao thức này đều có thể được ném qua throw. Các lỗi thường được định nghĩa dưới dạng enum tuân thủ Error.

Giao thức Error

Error là một giao thức rỗng (đánh dấu). Nó không yêu cầu triển khai phương thức, chỉ cần tuân thủ. Trình biên dịch Swift sử dụng Error để kiểm tra tĩnh: một hàm có throws chỉ có thể ném một giá trị tuân thủ Error.

Các hàm Throwing

Một hàm được đánh dấu bằng throws phải được gọi với try. Nếu một throw xảy ra bên trong hàm đó, quyền điều khiển được chuyển đến do-catch đã gọi nó. Nếu lỗi không được xử lý ở bất kỳ đâu trong chuỗi gọi, nó sẽ lan truyền lên cấp trên.

swift
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

rethrows là một bộ sửa đổi cho các hàm chấp nhận một closure throwing làm tham số. Nếu closure được truyền vào không ném lỗi, hàm rethrows có thể được gọi mà không cần try. Điều này cho phép viết các hàm bậc cao tổng quát tương thích với cả closure throwing và không throwing.

Cú pháp Do-Catch: Nhiều khối catch

Swift hỗ trợ nhiều khối catch với so khớp lỗi dựa trên kiểu. Điều này tương tự when trong Kotlin hoặc switch theo kiểu: catch đầu tiên khớp sẽ được thực thi, các catch còn lại bị bỏ qua.

So khớp theo kiểu

Mỗi khối catch có thể chứa một mẫu so khớp với một kiểu lỗi cụ thể. Swift kiểm tra các khối theo thứ tự, vì vậy các kiểu cụ thể hơn nên đứng trước các kiểu chung. Nếu không có mẫu nào khớp, khối catch không có mẫu sẽ được thực thi.

Xử lý nhiều kiểu

An toàn kiểu của Do-Catch cho phép xử lý từng lỗi riêng biệt hoặc kết hợp nhiều kiểu trong một khối thông qua so khớp mẫu phân cách bằng dấu phẩy. Điều này loại bỏ các chuỗi if-else cồng kềnh điển hình của Objective-C, nơi việc phân tích lỗi được thực hiện thông qua mã trả về hoặc NSError với kiểm tra miền và mã.

Swift cũng hỗ trợ catch where — so khớp với một điều kiện bổ sung. Ví dụ, bạn có thể lọc lỗi theo một mã hoặc văn bản cụ thể: catch URLError where error.code == .notConnectedToInternet. Điều này giảm số lượng khối catch trong khi duy trì độ chính xác.

Để gỡ lỗi các hàm throwing, Swift cung cấp sự phân biệt giữa lỗi và ngoại lệ. Một lỗi Swift là một giá trị trả về không yêu cầu mở xếp ngăn xếp. Các ngoại lệ Objective-C (@try/@catch) hoạt động ở cấp độ runtime và chỉ được sử dụng cho các lỗi nghiêm trọng. Do-Catch xử lý lỗi, không phải ngoại lệ, làm cho nó có thể dự đoán và hiệu quả.

Khi phát triển các thư viện và SDK, điều quan trọng là nhớ rằng các hàm throwing là một phần của hợp đồng công khai. Kiểu lỗi không được chỉ định trong chữ ký, vì vậy hãy ghi lại tài liệu những lỗi mà một hàm có thể ném trong các nhận xét hoặc thông qua các enum giống Result. Điều này giúp người dùng API viết các khối catch chính xác mà không cần xem xét triển khai.

Trong bối cảnh lập trình bất đồng bộ, Do-Catch với async/await giải quyết vấn đề callback hell. Trước đây trong Swift, việc xử lý lỗi trong mã bất đồng bộ yêu cầu các closure lồng nhau với kiểm tra Error? trong các trình xử lý hoàn thành. Với async/await và Do-Catch, mã throwing bất đồng bộ trông giống như mã đồng bộ, đơn giản hóa việc đọc và bảo trì các chuỗi yêu cầu phức tạp với xử lý lỗi ở mỗi bước.

swift
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("Lỗi không xác định: \(error)")
}

Các biến thể của try: try, try?, try! và khi nào sử dụng

Swift cung cấp ba cách để gọi các hàm throwing: try, try?try!. Mỗi tùy chọn giải quyết một nhiệm vụ cụ thể và có những hạn chế riêng.

try — Lời gọi tiêu chuẩn

try được sử dụng bên trong khối do, và lỗi được xử lý trong catch. Đây là cách chính để gọi các hàm throwing. Trình biên dịch yêu cầu try phải ở trong một ngữ cảnh mà lỗi có thể được bắt.

try? — Chuyển đổi thành Optional

try? chuyển đổi kết quả của một hàm throwing thành Optional. Nếu hàm ném lỗi, try? trả về nil, nếu không thì trả về Optional với giá trị thành công. Hữu ích cho các lời gọi mà lỗi có thể bỏ qua nhưng cần chỉ báo thành công.

try! — Thực thi bắt buộc

try! ngăn chặn việc xử lý lỗi. Nếu hàm throwing ném lỗi, ứng dụng sẽ kết thúc với lỗi runtime. Chỉ sử dụng try! khi bạn hoàn toàn chắc chắn rằng lỗi không thể xảy ra — ví dụ, khi tải một tài nguyên được đóng gói.

swift
// try? — bỏ qua lỗi, nhận được nil khi thất bại
if let data = try? Data(contentsOf: url) {
    processData(data)
}

// try! — chỉ khi lỗi được đảm bảo không thể xảy ra
let bundled = try! String(contentsOfFile: "Assets/default.txt")

// try — tùy chọn tiêu chuẩn trong do-catch
do {
    let result = try performNetworkRequest()
    updateUI(result)
} catch {
    showError(error)
}

Do-Catch trong phát triển iOS: Ví dụ thực tế

Do-Catch được sử dụng tích cực trong phát triển iOS để làm việc với các API hệ thống. Hãy xem xét các kịch bản điển hình: làm việc với hệ thống tệp, Core Data và các yêu cầu mạng.

Hệ thống tệp

Nhiều phương thức của FileManager là throwing. Do-Catch cho phép xử lý chính xác việc thiếu tệp, tài nguyên đang bận hoặc không đủ quyền.

Yêu cầu mạng với Codable

Phân tích cú pháp JSON qua JSONDecoder là một hoạt động throwing. Do-Catch bắt lỗi giải mã và lỗi mạng riêng biệt, cung cấp cho người dùng thông báo lỗi chính xác.

swift
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)
}

// Lời gọi với xử lý
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)
}

Các lỗi thường gặp với Do-Catch

Các nhà phát triển, đặc biệt là những người chuyển đổi từ các ngôn ngữ khác, thường mắc phải các lỗi đặc trưng khi sử dụng Do-Catch. Hãy xem xét những lỗi phổ biến nhất.

  • Quên try — gọi hàm throwing mà không có try gây ra lỗi biên dịch. Swift không cho phép bỏ qua lỗi một cách ngầm định.
  • Catch rỗng — khối catch không có xử lý sẽ che giấu lỗi. Luôn ghi log hoặc xử lý lỗi, ngay cả khi nó có vẻ không thể xảy ra.
  • Catch không có so khớp — một catch chung cho tất cả lỗi sẽ mất đi lợi ích của an toàn kiểu. Phân chia xử lý theo kiểu thông qua nhiều khối.
  • Sử dụng quá mức try! — try! trong mã sản xuất là nguồn gốc của các sự cố không mong đợi. Chỉ sử dụng cho các tài nguyên được đóng gói hoặc thử nghiệm.

Câu hỏi thường gặp

Do-Catch trong Swift khác gì so với try-catch trong các ngôn ngữ khác?

Trong Swift, lỗi là một giá trị tuân thủ giao thức Error, không phải là ngoại lệ với mở xếp ngăn xếp. Các hàm throwing phải được đánh dấu rõ ràng bằng throws và trình biên dịch yêu cầu try khi gọi — điều này loại bỏ các lỗi không được xử lý tại thời điểm biên dịch.

Có thể sử dụng Do-Catch với async/await không?

Có, các hàm async cũng có thể là throws và do-catch hoạt động với chúng. Gọi một hàm async throwing yêu cầu try await bên trong khối do. Đây là cách tiêu chuẩn để xử lý lỗi trong mã Swift bất đồng bộ.

Làm thế nào để xử lý lỗi mà không có do-catch?

Có các lựa chọn thay thế: try? chuyển đổi lỗi thành nil, try! gây ra sự cố khi có lỗi và rethrows cho phép truyền lỗi từ một closure. Do-catch vẫn là cách chính để xử lý rõ ràng.

Một hàm có thể ném các loại lỗi khác nhau không?

Swift không phân loại các lỗi được ném — bất kỳ hàm throwing nào cũng có thể ném bất kỳ kiểu nào tuân thủ Error. Phân chia xử lý thông qua so khớp trong catch theo các loại lỗi cụ thể.

Tôi có nên bọc tất cả các lỗi trong một kiểu tùy chỉnh không?

Có, đó là một thực hành tốt. Định nghĩa AppError như một enum tuân thủ Error và chuyển đổi các lỗi hệ thống thành lỗi miền. Điều này thống nhất việc xử lý và cô lập lớp ứng dụng khỏi các chi tiết hệ thống.

Tổng kết

  • Do-Catch là một cấu trúc Swift để xử lý lỗi với đánh dấu throws bắt buộc trên các hàm và try khi gọi.
  • Các lỗi Swift là các giá trị tuân thủ giao thức Error, không phải ngoại lệ với mở xếp ngăn xếp, cung cấp chi phí bằng không khi không có lỗi xảy ra.
  • Các khối nhiều catch cho phép xử lý các loại lỗi khác nhau riêng biệt với so khớp mẫu tương tự switch.
  • try? chuyển đổi lỗi thành nil cho các tình huống đơn giản, try! là lời gọi bắt buộc có nguy cơ sự cố, chỉ sử dụng cho các hoạt động được đảm bảo thành công.
  • Do-Catch với async/await là cách tiêu chuẩn để xử lý lỗi trong mã Swift bất đồng bộ sử dụng try await.
  • Tránh các khối catch rỗng và sử dụng quá mức try! — điều này che giấu lỗi và dẫn đến các sự cố không mong đợi trong sản xuất.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm