Как выполнить три асинхронные задачи последовательно (serial) в Swift?

«Как выполнить три асинхронные задачи последовательно (serial) в Swift?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Основные подходы:

1. Serial DispatchQueue Наиболее простой способ для задач, не возвращающих результат.

let serialQueue = DispatchQueue(label: "com.example.serialQueue")

serialQueue.async {
    print("Задача 1 начата")
    // Долгая операция...
    print("Задача 1 завершена")
}
serialQueue.async {
    print("Задача 2 начата") // Начнется строго после Задачи 1
    // Долгая операция...
    print("Задача 2 завершена")
}
serialQueue.async {
    print("Задача 3 начата") // Начнется строго после Задачи 2
    // Долгая операция...
    print("Задача 3 завершена")
}

2. OperationQueue с maxConcurrentOperationCount = 1 Позволяет добавлять зависимости между задачами.

let serialOperationQueue = OperationQueue()
serialOperationQueue.maxConcurrentOperationCount = 1

let op1 = BlockOperation { print("Задача 1") }
let op2 = BlockOperation { print("Задача 2") }
let op3 = BlockOperation { print("Задача 3") }

// Можно явно задать зависимости, даже при serial очереди
// op2.addDependency(op1)
// op3.addDependency(op2)

serialOperationQueue.addOperations([op1, op2, op3], waitUntilFinished: false)

3. Async/Await с циклом Современный способ для задач, возвращающих результат.

func performTasksSerially() async {
    let tasks: [() async -> Void] = [
        { await longRunningTask(id: 1) },
        { await longRunningTask(id: 2) },
        { await longRunningTask(id: 3) }
    ]

    for task in tasks {
        await task() // Каждая задача ожидает завершения предыдущей
    }
}

func longRunningTask(id: Int) async {
    print("Задача (id) начата")
    try? await Task.sleep(nanoseconds: 1_000_000_000) // Имитация работы
    print("Задача (id) завершена")
}

Ключевой момент: serialQueue.async не блокирует вызывающий поток, но задачи внутри очереди выполняются строго по порядку.