Как создать свою обертку для методов с completion, используя async/await?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание обертки для методов с completion с помощью async/await
Стандартный подход для преобразования методов с completion-блоками в async/await заключается в использовании континуаций (continuations). Это позволяет "подвесить" (suspend) выполнение асинхронной функции до получения результата из старого API.
Основная концепция континуаций
Swift предоставляет механизм withCheckedContinuation и withCheckedThrowingContinuation для безопасного преобразования callback-based API в async/await. Континуация создает "точку возобновления" (resumption point) для асинхронного выполнения.
Пример базовой обертки
Рассмотрим пример обертки для метода, который возвращает результат через completion:
// Старый метод с completion
func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion(.success("Данные получены"))
}
}
// Async обертка с использованием континуации
func fetchDataAsync() async throws -> String {
return await withCheckedThrowingContinuation { continuation in
fetchData { result in
switch result {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
Ключевые моменты реализации
-
Выбор типа континуации:
withCheckedContinuation— для методов без ошибокwithCheckedThrowingContinuation— для методов, которые могут завершиться с ошибкой
-
Обязательное возобновление: Континуация должна быть возобновлена (
resumed) строго один раз. Невыполнение этого правила приведет к утечке памяти и непредсказуемому поведению. -
Обработка completion: Внутри completion-блока старого метода мы вызываем
continuation.resume()с соответствующим результатом.
Более сложный пример с параметрами
// Старый метод с параметрами
func loadImage(url: URL, completion: @escaping (UIImage?) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
let image = UIImage(data: data ?? Data())
completion(image)
}.resume()
}
// Async обертка
func loadImageAsync(url: URL) async -> UIImage? {
return await withCheckedContinuation { continuation in
loadImage(url: url) { image in
continuation.resume(returning: image)
}
}
}
Расширенный подход: создание универсальной обертки
Для масштабирования решения можно создать универсальную функцию-обертку:
func asyncWrapper<T>(
for closure: @escaping (@escaping (T) -> Void) -> Void
) async -> T {
return await withCheckedContinuation { continuation in
closure { value in
continuation.resume(returning: value)
}
}
}
// Использование
let result = await asyncWrapper(for: fetchData)
Особенности и рекомендации
- Thread safety: Континуации безопасны относительно потоков, но важно обеспечить, что
resume()вызывается из любого потока - Cancellation: При использовании async/await поддерживается механизм отмены задач (
task cancellation), но для старого API нужно добавить собственную логику обработки отмены - Memory management: Убедитесь, что completion** не сохраняет континуацию** после вызова
resume() - Performance: Континуации добавляют минимальные накладные расходы, но значительно улучшают читаемость кода
Практическое применение в проектах
При переходе с completion-based API на async/await рекомендуется:
- Создать обертки для критически важных API
- Протестировать обертки на граничных случаях (ошибки, таймауты)
- Использовать обертки постепенно, не нарушая существующую логику
Итог
Континуации предоставляют мощный и безопасный механизм для интеграции старого callback-based кода с современным async/await. Это позволяет постепенно модернизировать проект без полного переписывания существующего API, сохраняя инвестиции в ранее написанный код.