Что такое Actor?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Actor в Swift — потокобезопасность нового поколения
Что такое Actor
Actor — это новый тип в Swift 5.5+, который обеспечивает потокобезопасность автоматически. Это альтернатива старым подходам с locks и semaphores.
actor UserManager {
private var users: [User] = []
func addUser(_ user: User) {
users.append(user) // Потокобезопасно автоматически
}
func getUsers() -> [User] {
users
}
}
Главное отличие от класса:
- Class может быть использован из нескольких потоков одновременно (race condition)
- Actor гарантирует эксклюзивный доступ к своему состоянию
Как это работает
actor Counter {
private var count = 0
func increment() {
count += 1 // Всегда безопасно, даже без locks
}
func getCount() -> Int {
count
}
}
// Использование
let counter = Counter()
// Нужно использовать await!
await counter.increment() // Ждём доступа
let value = await counter.getCount()
print(value)
Компилятор требует await — это напоминает о потокобезопасности
Actor vs Class с NSLock
Старый подход (NSLock):
class OldCounter {
private var count = 0
private let lock = NSLock()
func increment() {
lock.lock()
defer { lock.unlock() }
count += 1
}
}
Новый подход (Actor):
actor NewCounter {
private var count = 0
func increment() {
count += 1 // Никаких locks!
}
}
Аctor намного безопаснее и проще.
MainActor для UI
@MainActor
class UserViewModel: ObservableObject {
@Published var users: [User] = []
func loadUsers() {
// Гарантирует выполнение на главном потоке
DispatchQueue.global().async {
let users = self.fetchUsers() // фоне
Task { @MainActor in
self.users = users // автоматически на главном потоке
}
}
}
}
@MainActor — специальный Actor для главного потока
Практический пример: потокобезопасное хранилище
actor DataStore {
private var data: [String: Any] = [:]
private var accessCount = 0
func set(key: String, value: Any) {
data[key] = value
accessCount += 1
}
func get(key: String) -> Any? {
accessCount += 1
return data[key]
}
func getStats() -> (count: Int, keys: Int) {
(accessCount, data.count)
}
}
// Использование
let store = DataStore()
for i in 0..<100 {
Task {
await store.set(key: "key\(i)", value: i)
}
}
Task {
let stats = await store.getStats()
print("Access count: \(stats.count), Keys: \(stats.keys)")
}
Изоляция данных (Data Race Prevention)
// ❌ Ошибка компиляции!
class SharedData {
var value: Int = 0
}
let shared = SharedData()
Task {
shared.value = 1 // Data race!
}
Task {
shared.value = 2 // Data race!
}
// ✅ Правильно
actor SharedData {
var value: Int = 0
}
let shared = SharedData()
Task {
await shared.set(1) // Safe
}
Nonisolated членов Actor
actor User {
let id: String // Immutable, можно читать без await
nonisolated let createdAt: Date // Тоже безопасен
var name: String // Нужен await
init(id: String, name: String) {
self.id = id
self.name = name
self.createdAt = Date()
}
func updateName(_ newName: String) {
self.name = newName
}
}
let user = User(id: "123", name: "John")
print(user.id) // Без await (immutable)
await user.updateName("Jane") // С await (изменяемое)
SendableProtocol
// Можно отправлять между actors
struct Message: Sendable {
let text: String
let timestamp: Date
}
actor ChatRoom {
private var messages: [Message] = []
func send(_ message: Message) {
messages.append(message) // Message — Sendable
}
}
Сравнение подходов
| Подход | Безопасность | Сложность | Рекомендация |
|---|---|---|---|
| NSLock | Manual | Высокая | ❌ Старый |
| Serial Queue | Good | Средняя | ⚠️ Работает |
| Actor | Automatic | Низкая | ✅ Лучший выбор |
Когда использовать Actor
✅ Общее состояние, доступное из нескольких потоков ✅ Кэширование ✅ Database access wrappers ✅ Сетевые операции ✅ Любое потокобезопасное хранилище
❌ Immutable data — не нужен Actor ❌ Value types (struct) — уже потокобезопасны
Ключевые правила
✅ Используй Actor вместо NSLock и Serial Queue ✅ Всегда используй await при доступе к Actor ✅ Используй @MainActor для UI ✅ Помечай Sendable для типов, передаваемых между actors ❌ Не забывай про await — это не забывчивость, это безопасность
Actor — это будущее потокобезопасности в Swift. Это одна из лучших фич Swift 5.5+. Новый код должен использовать Actors вместо старых подходов.