← Назад к вопросам

Что такое actor в Swift и когда его использовать?

2.0 Middle🔥 201 комментариев
#CI/CD и инструменты разработки#Soft Skills и карьера

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

🎭 Что такое Actor в Swift?

Actor — это новый тип, представленный в Swift 5.5 как часть модели параллелизма (concurrency model). Он предназначен для изоляции состояния (state isolation) и безопасного управления доступом к изменяемым данным в многопоточной среде. Actor предотвращает гонки данных (data races) за счет гарантии, что в любой момент времени только одна задача имеет доступ к его изолированному состоянию.

Основные характеристики Actor:

  • Изоляция состояния: Все свойства actor изолированы по умолчанию. Доступ к ним возможен только внутри actor или через await.
  • Семантика ссылочного типа: Как и классы, actors являются ссылочными типами, но с гарантиями потокобезопасности.
  • Встроенная синхронизация: Компилятор автоматически добавляет проверки доступа, исключая необходимость вручную использовать locks или semaphores.
actor BankAccount {
    private var balance: Double = 0.0
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Double? {
        guard balance >= amount else { return nil }
        balance -= amount
        return amount
    }
    
    func currentBalance() -> Double {
        return balance
    }
}

🔍 Как работает изоляция?

Доступ к методам и свойствам actor возможен только асинхронно извне, через await. Внутри actor код выполняется последовательно на его внутреннем serial executor (последовательном исполнителе).

// Использование actor
let account = BankAccount()

Task {
    await account.deposit(amount: 1000)
    let currentBalance = await account.currentBalance()
    print("Balance: \(currentBalance)") // Balance: 1000.0
}

Если несколько задач пытаются получить доступ к одному actor, их запросы ставятся в очередь и выполняются последовательно, что исключает одновременную модификацию состояния.

🚀 Когда использовать Actor?

1. Защита разделяемого изменяемого состояния

Идеальный случай — когда у вас есть объект, содержащий состояние, которое может изменяться из разных потоков/задач.

actor Cache {
    private var storage: [String: Data] = [:]
    
    func set(_ data: Data, for key: String) {
        storage[key] = data
    }
    
    func get(for key: String) -> Data? {
        return storage[key]
    }
}

// Использование в разных задачах
let cache = Cache()

Task.detached {
    let imageData = await cache.get(for: "avatar")
}

Task.detached {
    let newData = Data()
    await cache.set(newData, for: "avatar")
}

2. Инкапсуляция ресурсов, требующих синхронизации

  • Доступ к базам данных
  • Работа с сетевыми запросами
  • Управление подключениями к внешним сервисам

3. Замена ручных механизмов синхронизации

Вместо использования DispatchQueue, locks, semaphores или @synchronized в Objective-C.

Старый подход:

class ThreadSafeCounter {
    private var count = 0
    private let queue = DispatchQueue(label: "com.example.counter")
    
    func increment() {
        queue.sync { count += 1 }
    }
    
    func getValue() -> Int {
        queue.sync { return count }
    }
}

Новый подход с actor:

actor Counter {
    private var count = 0
    
    func increment() {
        count += 1
    }
    
    func getValue() -> Int {
        return count
    }
}

4. Моделирование сущностей с внутренним состоянием

  • Игровые персонажи с изменяемыми характеристиками
  • Финансовые транзакции
  • Состояния устройств в IoT-системах

⚠️ Важные нюансы использования

Actor reentrancy

Actor допускает реентерабельность — при await внутри actor другая задача может получить к нему доступ. Это может привести к неожиданным состояниям:

actor AccountManager {
    private var accounts: [String: Double] = [:]
    
    func transfer(from: String, to: String, amount: Double) async -> Bool {
        guard let fromBalance = accounts[from], fromBalance >= amount else {
            return false
        }
        
        // ОПАСНО: здесь actor может быть приостановлен!
        await externalValidation() // ⏸️
        
        // Состояние может измениться к этому моменту
        guard let currentFromBalance = accounts[from], 
              currentFromBalance >= amount else {
            return false
        }
        
        accounts[from] = currentFromBalance - amount
        accounts[to, default: 0] += amount
        return true
    }
    
    private func externalValidation() async {
        // Длительная операция
    }
}

Global actors

Swift позволяет создавать глобальные actors для изоляции кода на глобальном уровне:

@MainActor
class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    func updateData() async {
        // Этот код гарантированно выполняется на main thread
        data = await fetchData()
    }
}

Производительность

Actors не бесплатны — они добавляют накладные расходы на синхронизацию. Для простых случаев может быть достаточно @Sendable closures или value types.

📊 Actor vs. Другие подходы

ПодходПотокобезопасностьПростотаПроизводительность
Actor✅ Автоматическая🟢 Высокая🟢 Оптимальная
Ручные locks✅ (если правильно)🔴 Низкая🟡 Переменная
DispatchQueue🟡 Средняя🟡 Хорошая
Value types✅ (невозможно разделение)🟢 Высокая🟢 Отличная

🎯 Практические рекомендации

  1. Начинайте с actor, когда нужна разделяемая изменяемая состояние между задачами
  2. Избегайте длительных операций внутри actor методов без await
  3. Используйте nonisolated для методов, не требующих доступа к состоянию actor:
    actor User {
        private var secretData: String
        let userId: String // Доступно без await
        
        nonisolated var displayId: String {
            return "User: \(userId)"
        }
    }
    
  4. Рассмотрите value types (structs) как альтернативу, если данные не требуют разделяемого изменяемого состояния

Actor — это мощная абстракция, которая делает конкурентный код в Swift более безопасным, предсказуемым и выразительным, устраняя целый класс ошибок времени выполнения, связанных с многопоточностью.