Sendable là một giao thức Swift đánh dấu các kiểu an toàn để truyền giữa các luồng. Khi làm việc với đồng thời (actor, async/await, Task), trình biên dịch Swift yêu cầu tất cả dữ liệu được truyền giữa các ngữ cảnh bị cô lập phải tuân thủ Sendable. Điều này ngăn chặn việc vô tình truyền các kiểu không an toàn có thể dẫn đến điều kiện tranh đua dữ liệu. Giao thức hoạt động như một hợp đồng: một kiểu tuân thủ Sendable đảm bảo không có trạng thái nội bộ không được đồng bộ hóa. Theo WWDC 2021, giao thức Sendable là một yếu tố bắt buộc khi thiết kế kiến trúc đa luồng an toàn.
Những điểm chính
Sendable là giao thức từ thư viện chuẩn Swift (SE-0302) đánh dấu các kiểu an toàn để truyền giữa các miền cô lập. Miền cô lập có nghĩa là actor, tác vụ Task hoặc ngữ cảnh @MainActor. Sendable không chứa yêu cầu nào — nó hoạt động như một đánh dấu an toàn.
Trước Swift 5.5, nhà phát triển có thể truyền bất kỳ đối tượng nào giữa các luồng qua DispatchQueue và trình biên dịch không kiểm tra xem nó có an toàn hay không. Sendable lấp đầy khoảng trống này: giờ đây, trình biên dịch tự theo dõi các truyền dẫn qua luồng và chặn những truyền dẫn không an toàn. Điều này làm cho mã đồng thời đáng tin cậy hơn ngay cả trước khi nó chạy.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
Trong ví dụ, UserProfile có thể được truyền an toàn giữa các luồng vì nó là một struct với các thuộc tính hằng của kiểu Sendable. NonSendableClass sẽ gây ra lỗi biên dịch khi cố gắng truyền qua await hoặc vào một Task.
Các kiểu Sendable trong Swift được chia thành ba loại: kiểu giá trị với thuộc tính Sendable, lớp final với trạng thái bất biến và hàm/closure được đánh dấu @Sendable. Trình biên dịch tự động suy ra sự tuân thủ Sendable cho các kiểu giá trị.
Struct, Enum và tuple tự động trở thành Sendable nếu tất cả thuộc tính và giá trị liên kết của chúng cũng là Sendable. Đây là cách tiếp cận thận trọng: nếu ít nhất một thuộc tính không tuân thủ Sendable, toàn bộ kiểu sẽ không được công nhận là an toàn.
| Kiểu | Sendable | Điều kiện |
|---|---|---|
| Struct | Có (không cần khai báo tường minh) | Tất cả thuộc tính là Sendable |
| Enum | Có (không cần khai báo tường minh) | Tất cả giá trị liên kết là Sendable |
| Final class | Có (cần khai báo tường minh) | Tất cả thuộc tính let là Sendable, không có var |
| Non-final class | Không | Không thể là Sendable do kế thừa |
Tất cả các kiểu tích hợp sẵn của Swift — Int, String, Double, Bool, Optional, Array, Dictionary, Set — đều tuân thủ Sendable. Điều này làm cho việc kết hợp kiểu trở nên an toàn theo mặc định. Nhà phát triển chỉ cần để ý đến các lớp tùy chỉnh.
@unchecked Sendable là cơ chế cho phép một lớp tự khai báo mình là Sendable một cách tường minh, bỏ qua các kiểm tra của trình biên dịch. Nhà phát triển chịu trách nhiệm về an toàn luồng của lớp đó. Điều này hữu ích cho các cầu nối Objective-C và cấu trúc được tối ưu hóa.
@unchecked Sendable được sử dụng khi một lớp đảm bảo an toàn nội bộ thông qua khóa hoặc thao tác nguyên tử, nhưng trình biên dịch không thể xác minh điều này một cách tĩnh. Ví dụ, một lớp với os_unfair_lock hoặc pthread_mutex_t — an toàn luồng của nó được đảm bảo bằng mã, nhưng Swift không nhìn thấy điều này.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
Việc sử dụng @unchecked Sendable cần được thực hiện cẩn thận. Đó là tín hiệu rõ ràng cho các nhà phát triển khác: "Tôi đã kiểm tra, nó an toàn." Lỗi trong triển khai có thể dẫn đến các điều kiện tranh đua khó phát hiện. Trước khi sử dụng @unchecked, hãy đảm bảo rằng kiểu thực sự không thể được viết lại dưới dạng kiểu giá trị.
Actor và Sendable là hai mặt của cùng một đồng xu. Actor cô lập trạng thái của nó, nhưng để trao đổi dữ liệu với thế giới bên ngoài, nó phải trả về các kiểu Sendable. Nếu một phương thức actor trả về kiểu không phải Sendable, trình biên dịch sẽ đưa ra cảnh báo hoặc lỗi.
Khi một actor gửi dữ liệu đến mã bên ngoài, dữ liệu này vượt qua ranh giới cô lập. Sendable đảm bảo rằng người nhận có thể sử dụng nó một cách an toàn bên ngoài actor. Bản thân actor vẫn bị cô lập — trạng thái nội bộ của nó không bị lộ.
struct AccountSnapshot: Sendable {
let id: UUID
let balance: Double
let lastUpdated: Date
}
actor BankActor {
private var balance: Double = 0
func snapshot() async -> AccountSnapshot {
return AccountSnapshot(
id: UUID(),
balance: balance,
lastUpdated: Date()
)
}
}
AccountSnapshot là một struct Sendable chỉ chứa các thuộc tính let của kiểu Sendable. Cách tiếp cận này là thực tiễn tốt nhất để trích xuất dữ liệu từ một actor. Ảnh chụp trạng thái được truyền theo giá trị và actor không mất quyền kiểm soát trạng thái của nó.
@Sendable là thuộc tính cho các hàm và closure đảm bảo rằng closure không capture dữ liệu không phải Sendable dưới dạng có thể thay đổi. Khi một closure được truyền vào một Task hoặc phương thức actor, nó phải là Sendable.
Trình biên dịch kiểm tra rằng closure @Sendable không capture tham chiếu có thể thay đổi đến các lớp. Việc capture thuộc tính let của kiểu Sendable được cho phép. Capture một biến var của kiểu tham chiếu sẽ gây ra lỗi vì closure có thể được thực thi đồng thời với việc thay đổi.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — capture var làm thay đổi counter, lỗi biên dịch
performAsync { // ❌ Thay đổi biến đã capture
print(constant)
}
Quy tắc rất đơn giản: một closure @Sendable chỉ có thể capture những dữ liệu mà bản thân chúng là Sendable và sẽ không bị thay đổi từ bên ngoài. Đối với các lớp, việc capture tham chiếu yếu weak self được cho phép nếu lớp không được đánh dấu là Sendable. Điều này ngăn chặn các vòng giữ tham chiếu kinh điển và điều kiện tranh đua.
Câu hỏi thường gặp
Sendable là một đánh dấu báo cho trình biên dịch: "kiểu này có thể được truyền an toàn giữa các luồng." Các struct và hằng thường là Sendable theo mặc định, các lớp thì không trừ khi được chỉ định rõ ràng.
Kiểu giá trị (struct, enum) với thuộc tính Sendable, lớp final với trạng thái bất biến và tất cả các kiểu Swift cơ bản: Int, String, Double, Bool, Array, Dictionary, Optional — tự động tuân thủ Sendable.
@unchecked Sendable là cách khai báo một lớp là Sendable mà không cần kiểm tra tĩnh từ trình biên dịch. Nhà phát triển tự đảm bảo an toàn, thường thông qua khóa hoặc thao tác nguyên tử.
Actor cô lập trạng thái của nó, nhưng khi trả dữ liệu cho mã bên ngoài, dữ liệu này vượt qua ranh giới cô lập. Sendable đảm bảo rằng người nhận sẽ không gặp điều kiện tranh đua khi sử dụng dữ liệu này bên ngoài actor.
Thêm giao thức Sendable vào một lớp final, tất cả thuộc tính của nó là hằng của kiểu Sendable. Nếu lớp sử dụng khóa, có thể dùng @unchecked Sendable, nhưng điều này đòi hỏi sự thận trọng.
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