Swift Concurrency - это не просто синтаксический сахар. Под капотом происходит сложная трансформация кода, управляемая компилятором и рантаймом. Давайте разберём ключевые компоненты системы.
Компилятор и State Machine.
Когда компилятор встречает async функцию, он разбивает ее на состояния (state machine). Каждый await становится точкой приостановки, между которыми код делится на блоки.
func fetchData() async -> Data {
let data = await downloadData()
let processedData = await process(data)
return processedData
}
Suspension Points (Точки приостановки).
Каждый await это место, где:
🔹 Стек сохраняется в кучу (heap).
🔹 Поток освобождается для других задач.
🔹 Создаётся continuation (информация для возобновления).
Особенности:
🔹 Приостановка не блокирует поток (в отличие от DispatchQueue).
🔹 Swift Runtime управляет жизненным циклом данных.
Continuation (Продолжение).
Это объект, который хранит:
🔹 Точку возобновления (какой await был последним).
🔹 Локальные переменные.
🔹 Что делать после получения результата.
Пример:
withCheckedContinuation { continuation in
someAsyncOperation { result in
continuation.resume(returning: result)
}
}
Executor (Исполнитель).
Определяет где и когда выполнять код после await. Могут быть следующие варианты:
🔹 Главным потоком (для MainActor).
🔹 Глобальным исполнителем (для обычных async функций).
🔹 Кастомным (для акторов).
Особенности:
🔹 Если функция запущена на MainActor, продолжение тоже выполнится на нём.
🔹 Глобальные функции используют кооперативный пул потоков.
Co-operative Thread Pool (Пул потоков).
Особенности:
🔹 Фиксированное количество потоков (обычно = числу ядер CPU).
🔹 Потоки не блокируются: при await они переключаются на другие задачи.
🔹 Управляется Swift Runtime, а не операционной системой.
Почему это эффективно?
🔹 Нет накладных расходов на создание потоков.
🔹 Автоматическая балансировка нагрузки.
Как это работает вместе?
🔹 Мы вызываем async функцию, после чего компилятор создаёт state machine.
🔹 При первом await: состояние и переменные сохраняются в continuation, поток возвращается в пул.
🔹 Когда результат готов, executor решает, где возобновить выполнение.
🔹 State machine продолжает работу с последней точки.
Пример с потоками:
func loadData() async {
// 1. Выполняется на случайном потоке из пула
let data = await fetchFromNetwork() // 2. Поток освобождается
// 3. Возобновляется на том же или другом потоке
await MainActor.run {
self.data = data // 4. Гарантированно на главном потоке
}
}
💡 Вывод:
🔸 Компилятор разбивает код на состояния.
🔸 Continuation хранит прогресс выполнения.
🔸 Executor выбирает поток для возобновления.
🔸 Пул потоков обеспечивает эффективное использование CPU.
Это делает Swift Concurrency легковесным и масштабируемым, в отличие от традиционных потоков.
➡️ Подписаться на канал
Мобильный трудоголик