← Назад к вопросам

Можно ли создать свою обертку для методов с completion, используя async/await?

2.2 Middle🔥 181 комментариев
#Многопоточность и асинхронность#Язык Swift

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли создать обертку для методов с completion, используя async/await?

Да, можно и даже рекомендуется для постепенной миграции кода или работы со старыми библиотеками. Это одна из ключевых возможностей Swift Concurrency, представленной в Swift 5.5.

Основной механизм: Continuations

Для создания оберток используются continuations — механизмы, позволяющие приостановить async-функцию и возобновить её, когда будет доступен результат. Swift предоставляет два типа:

  1. CheckedContinuation — выполняет проверки на правильное использование (не двойное возобновление, не утечку).
  2. UnsafeContinuation — более легковесный вариант без проверок, требует аккуратности.

Пример: обертка для метода с completion

Допустим, есть старый метод с completion:

class DataService {
    func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            completion(.success("Данные загружены"))
        }
    }
}

Создаем async-обертку:

extension DataService {
    func fetchDataAsync() async throws -> String {
        return try await withCheckedThrowingContinuation { continuation in
            fetchData { result in
                switch result {
                case .success(let data):
                    continuation.resume(returning: data)
                case .failure(let error):
                    continuation.resume(throwing: error)
                }
            }
        }
    }
}

Ключевые моменты реализации

1. Выбор типа continuation

  • Используйте withCheckedContinuation для обычных случаев (возврат значения).
  • withCheckedThrowingContinuation — если метод может вернуть ошибку.
  • Unsafe-версии — только при доказанных проблемах с производительностью.

2. Гарантии однократного возобновления

// ОПАСНО: может привести к крашу
fetchData { result in
    continuation.resume(returning: "data")
    continuation.resume(returning: "data") // Fatal error
}

3. Работа с отменой задач

func fetchDataWithCancellation() async throws -> String {
    return try await withTaskCancellationHandler {
        try await withCheckedThrowingContinuation { continuation in
            // ...
        }
    } onCancel: {
        // Вызываем отмену в оригинальном API, если поддерживается
        cancelFetch()
    }
}

Практический пример с сетью

// Старый метод
func downloadImage(url: URL, completion: @escaping (UIImage?, Error?) -> Void)

// Async-обертка
func downloadImageAsync(url: URL) async throws -> UIImage {
    return try await withCheckedThrowingContinuation { continuation in
        downloadImage(url: url) { image, error in
            if let error = error {
                continuation.resume(throwing: error)
            } else if let image = image {
                continuation.resume(returning: image)
            } else {
                continuation.resume(throwing: URLError(.badServerResponse))
            }
        }
    }
}

Важные ограничения и рекомендации

Ограничения:

  • Континуации нельзя использовать вне async-контекста
  • Каждое продолжение должно быть возобновлено ровно один раз
  • Не сохраняйте continuation в свойства — это приведет к утечке

Лучшие практики:

  1. Избегайте блокирующих вызовов внутри continuation-блоков
  2. Обрабатывайте отмену задач через withTaskCancellationHandler
  3. Тестируйте обертки на предмет double-resume и утечек
  4. Документируйте поведение обертки, особенно если оригинальный метод имеет побочные эффекты

Заключение

Создание оберток для методов с completion — мощный инструмент миграции, позволяющий:

  • Постепенно переводить кодbase на современный concurrency
  • Интегрироваться со старыми библиотеками и фреймворками
  • Упрощать цепочки асинхронных операций
  • Улучшать читаемость кода за счет линейного выполнения
// Было
service.fetchData { result in
    switch result {
    case .success(let data):
        process(data) { processed in
            // ...
        }
    }
}

// Стало
let data = try await service.fetchDataAsync()
let processed = try await processAsync(data)

Этот подход позволяет сочетать старый и новый код без необходимости немедленного переписывания всей кодовой базы, делая переход на Swift Concurrency постепенным и контролируемым процессом.

Можно ли создать свою обертку для методов с completion, используя async/await? | PrepBro