← Назад к вопросам
Можно ли создать свою обертку для методов с 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 предоставляет два типа:
CheckedContinuation— выполняет проверки на правильное использование (не двойное возобновление, не утечку).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 в свойства — это приведет к утечке
Лучшие практики:
- Избегайте блокирующих вызовов внутри continuation-блоков
- Обрабатывайте отмену задач через
withTaskCancellationHandler - Тестируйте обертки на предмет double-resume и утечек
- Документируйте поведение обертки, особенно если оригинальный метод имеет побочные эффекты
Заключение
Создание оберток для методов с 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 постепенным и контролируемым процессом.