Actor là một kiểu được giới thiệu trong Swift 5.5+ giúp giải quyết vấn đề điều kiện tranh chấp dữ liệu ở cấp độ ngôn ngữ. Khác với các khóa thủ công và hàng đợi DispatchQueue, actor tự động cô lập trạng thái của nó và đồng bộ hóa quyền truy cập vào nó. Điều này có nghĩa là hai luồng không thể đồng thời sửa đổi cùng một thuộc tính của kiểu actor, loại bỏ điều kiện tranh chấp mà không cần nỗ lực thêm từ nhà phát triển. Cơ chế này dựa trên khái niệm cô lập actor, nơi trình biên dịch theo dõi quyền truy cập vào các thuộc tính và phương thức của actor. Theo Apple, 2025, mô hình actor là một phần cơ bản của hệ thống đồng thời của Swift.
Những điểm chính
Actor là một kiểu tham chiếu, tương tự như lớp (class), nhưng có khả năng bảo vệ tự động chống lại các điều kiện tranh chấp dữ liệu. Nó được giới thiệu trong Swift 5.5 như một phần của hệ thống đồng thời cùng với async/await và Task. Actor đảm bảo rằng trạng thái có thể thay đổi của nó sẽ không bao giờ bị đọc hoặc ghi đồng thời từ các luồng khác nhau mà không có đồng bộ hóa rõ ràng.
Để khai báo một actor, sử dụng từ khóa actor theo sau là dấu ngoặc nhọn với các thành viên của nó. Về mặt cú pháp, actor giống một lớp, nhưng hành vi của nó khác biệt hoàn toàn.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
Trình biên dịch tự động cô lập tất cả các thuộc tính và phương thức của actor để chúng chỉ có thể truy cập được trong ngữ cảnh actor. Khi cố gắng truy cập balance từ bên ngoài actor, trình biên dịch sẽ tạo ra lỗi nếu cuộc gọi không được đánh dấu là async.
Cô lập dữ liệu là khái niệm chính của actor. Actor đảm bảo quyền truy cập loại trừ lẫn nhau vào trạng thái của nó thông qua cơ chế thực thi actor. Mỗi actor có bộ thực thi riêng xử lý tuần tự tất cả các truy cập vào các thành viên được cô lập của nó.
Khi mã bên ngoài actor gọi phương thức của nó, cuộc gọi được đặt vào hàng đợi của bộ thực thi actor. Actor chỉ thực thi một tác vụ tại một thời điểm, đảm bảo không có điều kiện tranh chấp. Nếu hai luồng cùng gọi deposit, cuộc gọi thứ hai sẽ chờ cuộc gọi đầu tiên hoàn thành.
let account = BankAccount(initialBalance: 1000.0)
// Cuộc gọi không đồng bộ — yêu cầu từ bên ngoài actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
Mỗi cuộc gọi đến phương thức actor đều yêu cầu await vì actor có thể đang bận với một tác vụ khác. Đây không phải là lỗi mà là một thiết kế có chủ đích nhằm ngăn chặn các điều kiện tranh chấp. Swift ngầm làm cho các getter và setter của thuộc tính actor trở thành async, vì vậy việc đọc một thuộc tính cũng yêu cầu await.
Actor và lớp (class) đều là kiểu tham chiếu, nhưng hành vi của chúng trong môi trường đa luồng khác nhau hoàn toàn. Một class không cung cấp bất kỳ khả năng bảo vệ tự động nào chống lại các điều kiện tranh chấp, trong khi actor tích hợp nó ở cấp trình biên dịch thông qua hệ thống kiểu.
| Đặc điểm | Actor | Class |
|---|---|---|
| Bảo vệ tranh chấp | Tự động, ở cấp trình biên dịch | Yêu cầu đồng bộ hóa thủ công |
| Truy cập thuộc tính | Chỉ qua await từ bên ngoài | Trực tiếp, không đồng bộ hóa |
| Kế thừa | Chỉ từ các actor khác | Kế thừa lớp tiêu chuẩn |
| Tuân thủ giao thức | Có thể tuân thủ giao thức | Tiêu chuẩn |
| Hiệu suất | Chi phí thấp với cô lập | Nhanh hơn không đồng bộ hóa |
Một actor chỉ có thể kế thừa từ một actor khác và không thể kế thừa từ một class. Điều này có chủ đích vì class thiếu cơ chế cô lập actor và việc trộn lẫn các kiểu sẽ phá vỡ các đảm bảo an toàn.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
Các cuộc gọi không đồng bộ là cơ chế tương tác với actor từ mã bên ngoài. Vì actor cô lập trạng thái của nó, bất kỳ truy cập nào vào các thành viên của nó từ bên ngoài đều yêu cầu await. Điều này cho phép Swift đảm bảo rằng mã gọi không chặn luồng và actor có thể xử lý các yêu cầu khác.
Ngoài các kiểu actor đã khai báo, Swift hỗ trợ actor toàn cục — thuộc tính @MainActor, đánh dấu các lớp, thuộc tính hoặc phương thức có thể thực thi trên luồng chính. Điều này đặc biệt hữu ích cho mã UI trong các ứng dụng iOS.
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// Mã này được đảm bảo chạy trên luồng chính
title = "New Title"
}
}
Sử dụng @MainActor loại bỏ nhu cầu gọi thủ công DispatchQueue.main.async, làm cho mã sạch hơn và an toàn hơn. Trình biên dịch đảm bảo rằng việc chuyển sang luồng chính diễn ra chính xác.
Nonisolated là một từ khóa cho phép đánh dấu một phương thức hoặc thuộc tính được tính toán của actor là không bị cô lập. Các thành viên này không có quyền truy cập vào trạng thái bị cô lập của actor, nhưng có thể được gọi mà không cần await từ bên ngoài actor.
Các phương thức nonisolated hữu ích cho các tính toán không phụ thuộc vào trạng thái có thể thay đổi của actor. Ví dụ, phương thức formatBalance không truy cập trực tiếp vào balance, mà chỉ định dạng giá trị được truyền vào — phương thức như vậy an toàn để đặt là nonisolated.
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
Các thành viên nonisolated thực thi đồng bộ và không yêu cầu await. Tuy nhiên, chúng không thể đọc trực tiếp các thuộc tính bị cô lập của actor. Nếu một phương thức nonisolated cần một giá trị từ actor, nó phải được truyền dưới dạng tham số.
Reentrancy là một cơ chế cho phép nhập lại vào một actor trong khi chờ một cuộc gọi không đồng bộ. Nếu không có reentrancy, một actor có thể bị bế tắc vĩnh viễn nếu một trong các phương thức của nó chờ một phương thức khác mà phương thức đó lại chờ phương thức đầu tiên.
Khi mã bên trong actor thực thi await, actor tạm dừng tác vụ hiện tại và có thể xử lý một tác vụ khác trong hàng đợi. Sau khi await hoàn thành, tác vụ được tiếp tục. Điều này ngăn chặn bế tắc nhưng đòi hỏi sự thận trọng: trạng thái của actor giữa các điểm await có thể thay đổi.
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — điểm nhập lại
let result = await fetchData(id: id)
// Bộ nhớ đệm có thể đã thay đổi sau await — kiểm tra lại
cache[id] = result
return result
}
}
Các nhà phát triển phải tính đến reentrancy và kiểm tra trạng thái của actor sau các điểm await. Một lỗi điển hình là cho rằng cô lập actor tồn tại qua các điểm tạm dừng không đồng bộ. Trong thực tế, giữa await và câu lệnh tiếp theo, trạng thái có thể khác so với dự kiến.
Câu hỏi thường gặp
Actor tự động cô lập trạng thái của nó khỏi các điều kiện tranh chấp, yêu cầu await để truy cập từ bên ngoài. Class không cung cấp sự bảo vệ như vậy — nhà phát triển tự chịu trách nhiệm đồng bộ hóa qua khóa hoặc hàng đợi. Actor chỉ kế thừa từ actor, class kế thừa từ class.
Có, một actor có thể kế thừa từ một actor khác. Lớp con nhận được tất cả các thuộc tính và phương thức được cô lập của cha. Actor không thể kế thừa từ class vì các class thiếu cơ chế cô lập actor ở cấp trình biên dịch.
Ngữ cảnh actor-isolated là một vùng mã nơi cho phép truy cập trực tiếp vào trạng thái có thể thay đổi của actor. Bên trong các phương thức actor được đánh dấu là bị cô lập (mặc định), có thể đọc và ghi các thuộc tính mà không cần await. Trình biên dịch kiểm tra các ranh giới cô lập.
Dữ liệu từ actor được truyền qua phương thức async trả về kiểu Sendable, hoặc qua phương thức nonisolated chấp nhận giá trị dưới dạng tham số. Cũng có thể tạo một thuộc tính async trả về ảnh chụp nhanh trạng thái của actor dưới dạng cấu trúc Sendable.
Có, actor có thể tuân thủ các giao thức. Nếu một giao thức chứa yêu cầu bị cô lập (actor-isolated), chúng tự động trở thành bị cô lập với actor. Đối với các phương thức không đồng bộ trong giao thức, có thể chỉ định rằng chúng phải được gọi trên một actor cụ thể bằng cách sử dụng đánh dấu isolated.
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