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

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

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

Начать курс

Введение в конкурентность и многопоточность в Swift

Современные мобильные и десктопные приложения должны быть быстрыми и отзывчивыми. В разработке под iOS и macOS ключевую роль играет правильное управление конкурентностью и многопоточностью. Swift предоставляет мощные инструменты для параллельного выполнения задач, начиная от классического Grand Central Dispatch (GCD) и заканчивая современной моделью async/await и акторами.

В этой статье мы разберем все основные подходы к работе с потоками в Swift: от базовых концепций до продвинутых техник. Вы узнаете, как избежать race conditions, deadlocks и других проблем, характерных для многопоточного программирования.

Grand Central Dispatch (GCD) — классика многопоточности

Grand Central Dispatch (GCD) — это низкоуровневая библиотека Apple для управления параллельным выполнением кода. Она использует очереди (dispatch queues) для распределения задач по потокам.

Основные типы очередей

В GCD существует два основных типа очередей:

  • Serial queues (последовательные): задачи выполняются одна за другой в порядке добавления. Идеально подходят для синхронизации доступа к ресурсам.
  • Concurrent queues (параллельные): задачи могут выполняться одновременно, порядок завершения не гарантируется.

Пример работы с GCD

import Foundation

// Создаем последовательную очередь
let serialQueue = DispatchQueue(label: "com.example.serial")

// Создаем параллельную очередь
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

// Асинхронное выполнение
serialQueue.async {
    print("Задача 1 выполняется в фоне")
}

// Синхронное выполнение (блокирует текущий поток)
serialQueue.sync {
    print("Задача 2 выполняется синхронно")
}

// Работа с главным потоком
DispatchQueue.main.async {
    print("Обновление UI на главном потоке")
}

// Задержка выполнения
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Выполнено через 2 секунды")
}

Dispatch Group — синхронизация группы задач

Dispatch Group позволяет отслеживать завершение группы асинхронных задач и выполнить код после их окончания.

let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .background)

for i in 1...5 {
    queue.async(group: group) {
        print("Задача \(i) началась")
        Thread.sleep(forTimeInterval: Double.random(in: 0.5...2.0))
        print("Задача \(i) завершена")
    }
}

group.notify(queue: DispatchQueue.main) {
    print("Все задачи выполнены! Обновляем UI.")
}

Современный async/await в Swift 5.5+

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

Определение асинхронных функций

Асинхронные функции помечаются ключевым словом async, а их вызов требует await:

import Foundation

// Асинхронная функция загрузки данных
func fetchUserData() async throws -> String {
    let url = URL(string: "https://api.example.com/user")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? "No data"
}

// Использование async/await
func loadUserProfile() async {
    do {
        let userData = try await fetchUserData()
        await MainActor.run {
            print("Данные пользователя: \(userData)")
        }
    } catch {
        print("Ошибка загрузки: \(error)")
    }
}

// Запуск асинхронной задачи
Task {
    await loadUserProfile()
}

Async let — параллельное выполнение

Ключевое слово async let позволяет запускать несколько асинхронных задач параллельно:

func loadMultipleResources() async throws -> (String, String) {
    async let resource1 = fetchUserData()
    async let resource2 = fetchResource2()
    
    // Ожидаем завершения обеих задач
    return try await (resource1, resource2)
}

Task {
    let (data1, data2) = try await loadMultipleResources()
    print("Оба ресурса загружены: \(data1), \(data2)")
}

Акторы (Actors) — безопасная работа с общими данными

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

Создание актора

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

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

Task {
    await account.deposit(amount: 1000)
    let success = await account.withdraw(amount: 500)
    print("Снятие успешно: \(success)")
    
    let currentBalance = await account.getBalance()
    print("Текущий баланс: \(currentBalance)")
}

Преимущества акторов

  • Автоматическая синхронизация доступа к свойствам и методам
  • Предотвращение гонок данных на уровне компилятора
  • Упрощение кода по сравнению с ручной синхронизацией через GCD
  • Интеграция с async/await для удобного асинхронного вызова

OperationQueue — высокоуровневый API для управления задачами

OperationQueue предоставляет более высокий уровень абстракции по сравнению с GCD. Вы работаете с объектами Operation, которые можно настраивать, отменять и связывать между собой.

Базовый пример с OperationQueue

import Foundation

// Создаем очередь операций
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 2 // Ограничиваем до 2 параллельных операций

// Создаем операции
let operation1 = BlockOperation {
    print("Операция 1 началась")
    Thread.sleep(forTimeInterval: 1.0)
    print("Операция 1 завершена")
}

let operation2 = BlockOperation {
    print("Операция 2 началась")
    Thread.sleep(forTimeInterval: 1.5)
    print("Операция 2 завершена")
}

// Устанавливаем зависимость: operation2 выполнится только после operation1
operation2.addDependency(operation1)

// Добавля                

Блоги

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