Конкурентность и многопоточность в Swift: продвинутые техники

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Конкурентность и многопоточность в Swift: продвинутый уровень

Конкурентность (concurrency) — одна из самых сложных и важных тем в разработке под iOS/macOS. На продвинутом уровне Swift предлагает мощные инструменты: от классического Grand Central Dispatch (GCD) до современной модели async/await и строгой системы Actors. В этой статье мы разберем, как эффективно управлять потоками, избегать гонок данных и писать потокобезопасный код.

1. Grand Central Dispatch (GCD): работа с очередями и группами

GCD остается основой многопоточности в Swift. Однако на продвинутом уровне важно понимать не только разницу между main и global(), но и уметь управлять зависимостями задач.

Создание собственных очередей

let serialQueue = DispatchQueue(label: "com.pythonlib.serial")
let concurrentQueue = DispatchQueue(label: "com.pythonlib.concurrent", attributes: .concurrent)

serialQueue.async {
    print("Задача 1 в последовательной очереди")
}
concurrentQueue.async {
    print("Задача 2 в параллельной очереди")
}

DispatchGroup: синхронизация нескольких задач

let group = DispatchGroup()
let queue = DispatchQueue.global()

queue.async(group: group) {
    sleep(1)
    print("Загрузка данных 1")
}
queue.async(group: group) {
    sleep(2)
    print("Загрузка данных 2")
}

group.notify(queue: DispatchQueue.main) {
    print("Все загрузки завершены, обновляем UI")
}

Используйте DispatchGroup, когда нужно дождаться выполнения нескольких параллельных операций (например, загрузка картинок или парсинг JSON).

2. Современная модель async/await и Task

Начиная с Swift 5.5, язык получил нативную поддержку асинхронности через ключевые слова async и await. Это упрощает чтение кода и избавляет от "пирамиды замыканий".

Объявление и вызов асинхронной функции

func fetchUserData() async -> String {
    // Имитация сетевого запроса
    try? await Task.sleep(nanoseconds: 2_000_000_000)
    return "User data loaded"
}

Task {
    let result = await fetchUserData()
    print(result) // Выведет "User data loaded"
}

Параллельные вызовы с async let

func loadImage() async -> Data { return Data() }
func loadMetadata() async -> String { return "meta" }

Task {
    async let image = loadImage()
    async let metadata = loadMetadata()
    
    let result = try await (image, metadata)
    print("Изображение: \(result.0), метаданные: \(result.1)")
}

async let позволяет запустить несколько асинхронных задач параллельно и дождаться их всех. Это эффективнее последовательного вызова.

3. Actors: потокобезопасность на уровне языка

Акторы (Actors) — это новый механизм защиты от гонок данных. Они гарантируют, что доступ к их состоянию будет синхронизирован автоматически.

Определение и использование актора

actor BankAccount {
    private var balance: Double = 0.0
    
    func deposit(amount: Double) {
        balance += amount
        print("Баланс после пополнения: \(balance)")
    }
    
    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }
    
    func getBalance() -> Double { balance }
}

Task {
    let account = BankAccount()
    await account.deposit(amount: 100)
    let success = await account.withdraw(amount: 50)
    print("Снятие успешно: \(success)")
}

Все методы актора по умолчанию изолированы — вызывать их можно только через await. Это исключает случайное изменение данных из нескольких потоков.

Неизолированные методы (nonisolated)

actor Logger {
    nonisolated func log(message: String) {
        print("Лог: \(message)")
    }
}

// Вызов без await
let logger = Logger()
logger.log(message: "Тестовое сообщение")

Используйте nonisolated для методов, которые не обращаются к изменяемому состоянию актора.

4. Практические советы и ловушки

  • Не блокируйте main thread. Всегда выносите тяжелые вычисления (сеть, базы данных, обработка изображений) в фоновые очереди или асинхронные функции.
  • Избегайте deadlock'ов. Не вызывайте .sync на той же очереди, которая уже выполняется — это приведет к взаимной блокировке.
  • Используйте @MainActor для обновления UI. Пометьте класс или метод этим атрибутом, чтобы гарантировать выполнение на главном потоке.
  • Не злоупотребляйте глобальными переменными. Если нужно общее состояние, используйте акторы или сериализованные очереди.
  • Тестируйте с Thread Sanitizer. Включите санитайзер в Xcode (Product → Scheme → Edit Scheme → Diagnostics → Thread Sanitizer) для выявления гонок данных.

Заключение

Конкурентность в Swift — это не просто набор инструментов, а целая парадигма. От GCD до async/await и Actors — каждый уровень абстракции решает свои задачи. На продвинутом уровне важно не только знать синтаксис, но и понимать, как работают очереди, планировщики и изоляция данных. Используйте акторы для потокобезопасности, async/await для читаемости и GCD для тонкой настройки. Следуя этим принципам, вы сможете писать быстрые, стабильные и масштабируемые приложения для iOS и macOS.

Блоги

Рекомендаций по книгам