defer là một cấu trúc điều khiển luồng trong Swift, lên lịch thực thi một khối mã tại thời điểm thoát khỏi phạm vi hiện tại. Khối defer được thực thi bất kể phạm vi kết thúc như thế nào — return, break, throw, fatalError hoặc kết thúc bình thường. Theo Swift Language Guide (2025), khi có nhiều defer trong cùng một phạm vi, chúng được thực thi theo thứ tự ngược với khai báo — defer được khai báo cuối cùng sẽ chạy đầu tiên (LIFO). Điều này làm cho defer không thể thiếu để dọn dẹp tài nguyên đảm bảo: đóng bộ mô tả tệp, giải phóng khóa, giải phóng con trỏ tạm thời mà không có nguy cơ bỏ lỡ việc dọn dẹp khi thoát sớm.
Những Điểm Chính
defer là một cấu trúc điều khiển luồng trong Swift, được giới thiệu trong Swift 2.0 (2015), trì hoãn việc thực thi khối của nó cho đến khi phạm vi hiện tại kết thúc. Tính năng chính: defer đảm bảo rằng phần thân của nó được thực thi bất kể phạm vi kết thúc như thế nào — thành công (return), có lỗi (throw), sớm (break, continue) hoặc nghiêm trọng (fatalError, precondition).
Về mặt cú pháp, defer được viết như defer { /* mã */ } và có thể được đặt ở bất kỳ đâu trong phạm vi. Trình biên dịch Swift đảm bảo rằng mã bên trong defer sẽ được thực thi ngay cả khi có ngoại lệ hoặc return xảy ra giữa khai báo defer và kết thúc phạm vi. Điều này về cơ bản phân biệt defer với mã thông thường được đặt ở cuối hàm, có thể bị bỏ qua khi thoát sớm.
Theo một bài báo của Chris Lattner (Người tạo ra Swift, 2015), defer được lấy cảm hứng từ các cấu trúc tương tự trong các ngôn ngữ khác — defer trong Go, finally trong Java/Python, scope guard trong C++ — nhưng có một điểm khác biệt quan trọng: trong Swift, defer thực thi ở cuối phạm vi, không phải ngay sau khối try-catch. Điều này cung cấp hành vi dễ dự đoán hơn cho việc dọn dẹp trong các hàm có nhiều điểm thoát.
Sử dụng defer để quản lý tài nguyên đối xứng: mở tệp → defer { close }, giành khóa → defer { unlock }. Mẫu này đảm bảo rằng việc giải phóng tài nguyên không bao giờ bị bỏ lỡ trong bất kỳ trường hợp nào.
Khi nhiều defer được khai báo trong cùng một phạm vi, chúng được thực thi theo thứ tự ngược với khai báo (LIFO — Last In, First Out). Điều này có nghĩa là defer được khai báo cuối cùng chạy đầu tiên và defer đầu tiên chạy cuối cùng:
func exampleDeferOrder() {
defer { print("Defer thứ nhất") }
defer { print("Defer thứ hai") }
defer { print("Defer thứ ba") }
print("Thân hàm")
}
// Đầu ra:
// Thân hàm
// Defer thứ ba
// Defer thứ hai
// Defer thứ nhất
Thứ tự LIFO rất quan trọng để quản lý chính xác các tài nguyên lồng nhau. Nếu tệp A được mở trước, sau đó tệp B, chúng phải được giải phóng theo thứ tự ngược lại: đầu tiên B, sau đó A. Với defer, điều này xảy ra tự động — hãy khai báo defer ngay sau khi mở mỗi tài nguyên và thứ tự dọn dẹp sẽ đúng bất kể số lượng điểm thoát trong hàm.
Theo Swift by Sundell (2024), tính năng này làm cho defer trở nên lý tưởng cho các khóa lồng nhau và giao dịch: giành khóa → defer { unlock } → giành khóa tiếp theo → defer { unlock }. LIFO đảm bảo các khóa được giải phóng theo thứ tự ngược với giành khóa, ngăn chặn deadlock.
Trường hợp sử dụng chính của defer là dọn dẹp tài nguyên đảm bảo. Hãy xem xét làm việc với hệ thống tệp. Mở tệp qua FileHandle yêu cầu đóng rõ ràng — defer đảm bảo rằng close sẽ được gọi trong bất kỳ kịch bản nào:
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() sẽ được gọi ngay cả khi throw hoặc return
}
Một kịch bản điển hình khác là hoạt ảnh UI với cờ tải. Trước khi bắt đầu tải, hãy đặt cờ isLoading = true và defer đặt lại thành false khi thoát khỏi hàm, bất kể yêu cầu thành công hay thất bại. Điều này ngăn cờ vẫn là true do lỗi không được xử lý, có thể chặn giao diện vĩnh viễn.
Theo Bitbucket Engineering Blog (2024), defer cũng được sử dụng để phân tích hiệu năng: ghi lại thời gian ở đầu hàm và trong defer — tính toán và xuất ra chênh lệch. Điều này cung cấp các phép đo hiệu suất chính xác cho tất cả các đường dẫn thực thi, bao gồm cả những đường dẫn bị lỗi.
defer hoạt động hiệu quả với các hàm throws. Khi một hàm có thể ném lỗi ở bất kỳ giai đoạn nào, defer đảm bảo dọn dẹp mà không nhân đôi mã trong mỗi khối catch hoặc thoát sớm bằng 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) và logAudit(user) sẽ được gọi
// khi có throw hoặc return
}
Quan trọng: defer được thực thi trước khi chuyển quyền điều khiển ra khỏi khối catch, nhưng sau khi lỗi xảy ra. Nếu một lỗi được ném bên trong defer, Swift không cho phép sử dụng try trực tiếp bên trong defer — bạn cần try? hoặc try!. Theo Tài liệu của Apple, Swift không cho phép lỗi thoát ra khỏi defer, vì điều đó sẽ vi phạm sự đảm bảo thực thi khối.
Đặt defer ngay sau khi giành được tài nguyên. Điều này tuân theo nguyên tắc gần gũi: người đọc thấy việc giành và giải phóng cùng nhau, cải thiện độ tin cậy của mã và đơn giản hóa việc xem xét mã.
defer được thực thi khi thoát khỏi phạm vi mà nó được khai báo. Nếu defer được khai báo bên trong khối do, nó được thực thi khi thoát khỏi khối đó, không phải hàm bên ngoài. Nếu nằm trong vòng lặp for — tại mỗi lần lặp:
func scopeExample() {
print("start")
do {
defer { print("defer khối do") }
print("inside do")
}
// "defer khối do" in ở đây
print("after do")
}
// Đầu ra: start, inside do, defer khối do, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// Đầu ra: iteration 1, end iteration 1, iteration 2, end iteration 2, ...
Các biến được defer chụp được đọc tại thời điểm thoát khỏi phạm vi, không phải tại thời điểm khai báo defer. Nếu một biến thay đổi giữa khai báo defer và kết thúc phạm vi, defer sẽ thấy giá trị mới nhất. Đây là điểm khác biệt quan trọng so với closure, nơi việc chụp xảy ra tại thời điểm tạo. Hãy cẩn thận: các thay đổi đối với biến sau khi khai báo defer sẽ ảnh hưởng đến việc thực thi của nó.
Lỗi đầu tiên — giả định thứ tự thực thi khác với LIFO. Nếu thứ tự dọn dẹp quan trọng và defer được khai báo sai thứ tự, tài nguyên có thể được giải phóng vi phạm phụ thuộc. Giải pháp: hãy khai báo defer ngay sau khi chụp mỗi tài nguyên. Tài nguyên thứ hai được mở → defer { close second } trước khi tài nguyên đầu tiên được đóng.
Lỗi thứ hai — sử dụng defer cho logic không liên quan đến dọn dẹp. defer dành cho việc dọn dẹp đảm bảo, không phải cho kiểm soát luồng chính. Nếu mã bên trong defer ảnh hưởng đến giá trị trả về, đó hầu như luôn là lỗi. defer không thể thay đổi giá trị trả về của hàm (không giống như Java finally, nơi return trong finally ghi đè return gốc).
Lỗi thứ ba — ném lỗi từ defer. Swift cấm try bên trong defer nếu lỗi có thể lan truyền ra ngoài. Sử dụng try? hoặc try! cho các thao tác có thể ném lỗi hoặc bọc chúng trong một hàm riêng không có throws. Theo O’Reilly “Swift in Depth” (2025), thực hành tốt là làm cho các hàm dọn dẹp không ném lỗi (non-throwing) hoặc xử lý lỗi bên trong defer.
Các Câu Hỏi Thường Gặp
defer là một cấu trúc Swift trì hoãn việc thực thi một khối cho đến khi phạm vi hiện tại kết thúc. Khối luôn được thực thi — khi return, throw, break hoặc kết thúc bình thường. Nó được sử dụng để dọn dẹp tài nguyên đảm bảo: đóng tệp, giải phóng khóa.
Theo thứ tự khai báo ngược (LIFO) — defer được khai báo cuối cùng chạy đầu tiên. Điều này đảm bảo dọn dẹp chính xác các tài nguyên lồng nhau: nếu tài nguyên B được mở sau A, nó sẽ được đóng trước A, ngăn chặn sự phụ thuộc vào các tài nguyên đã được giải phóng.
Không trực tiếp — Swift ngăn chặn việc lan truyền lỗi từ defer. Sử dụng try? hoặc try! cho các thao tác có thể ném lỗi. Thực hành tốt nhất là làm cho các hàm dọn dẹp không ném lỗi hoặc xử lý lỗi bên trong defer mà không lan truyền ra ngoài.
defer gắn với một phạm vi và thực thi khi bất kỳ sự thoát nào, bao gồm return, throw và break. finally (trong các ngôn ngữ khác) gắn với try-catch và chỉ thực thi khi có try. Swift không có finally — defer bao phủ hoàn toàn kịch bản này và hoạt động cho bất kỳ phạm vi nào, không chỉ xử lý lỗi.
Có, defer đọc các biến tại thời điểm thoát khỏi phạm vi, không phải tại thời điểm khai báo. Nếu một biến thay đổi sau khi defer được khai báo, khối defer sẽ thấy giá trị mới nhất. Điều này khác với closure thông thường, nơi việc chụp được cố định tại thời điểm tạo.
Tổng Kế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.
Đọc thêm