Перейти к содержанию

1. Continuation

Мы уже говорили с вами про continuation. Здесь мы рассмотрим его применение немного в другом контексте. Предположим у нас есть метод, который отдаёт нам замыкание, а мы хотим сделать async функцию. Как быть в такой ситуации?

func loadData(by id: Int, completion: @escaping (Data) -> Void) { ... }


func fetchVideo(by id: Int) async -> Video {
    await withCheckedContinuation { continuation in
        loadData(by: id) { data in
             continuation.resume(returning: data)
        }
    }
}
Мы выстроили связь между функциями использующими комплишины и async. Что нужно знать и помнить?

Типы Continuation

1) Checked Continuation (Checked Throwing Continuation) -- проверяют, что resume вызывается ровно один раз, предотвращая потенциальные ошибки выполнения

2) Unsafe Continuation (Unsafe Throwing Continuation) -- не включают такую проверку, но могут быть немного эффективнее. В данном случае эта ответственность лежит на разработчике, если вызвать resume повторно, то будет либо неопределённое поведение, либо креш.

RESUME в continuation ДОЛЖЕН БЫТЬ ВЫЗВАН РОВНО ОДИН РАЗ.

Отмена задач c continuation

  • Шаг 1: Проверка отмены задачи Перед тем, как возобновить выполнение с помощью continuation, проверьте, была ли задача отменена, используя Task.isCancelled. Если задача была отменена, вы можете завершить выполнение continuation без выполнения дальнейшей работы.

  • Шаг 2: Отмена выполняемой операции Если ваша асинхронная операция поддерживает отмену (например, сетевой запрос с использованием URLSessionTask), вы должны явно отменить эту операцию, когда обнаружите, что связанная с ней задача была отменена.

Пример:

func fetchData(url: URL) async -> Data? {
    await withCheckedContinuation { continuation in
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            // Проверяем, была ли задача отменена
            guard !Task.isCancelled else {
                continuation.resume(throwing: CancellationError())
                return
            }

            // Возвращаем данные или ошибку
            if let data = data {
                continuation.resume(returning: data)
            } else if let error = error {
                continuation.resume(throwing: error)
            } else {
                continuation.resume(throwing: URLError(.badServerResponse))
            }
        }

        // Запускаем сетевой запрос
        task.resume()

        // Обработка отмены задачи
        continuation.onTermination = { _ in
            // Отменяем сетевой запрос, если задача была отменена
            task.cancel()
        }
    }
}

// Использование
let url = URL(string: "https://example.com")!
let task = Task {
    if let data = await fetchData(url: url) {
ружены: \(data)")
    } else {
        print("Ошибка загрузки данных или задача была отменена")
    }
}

// Отмена задачи
task.cancel()