Добавить объявление

Как превратить completion handlers в современный async/await

Swift Concurrency с async/await стал стандартом современной iOS-разработки, но вокруг нас все еще живут тонны кода, написанного на completion handlers. Вместо болезненного рефакторинга всего легаси можно построить элегантный мост между старым и новым кодом. Сегодня разберем, как превратить старый код с устаревшим подходом в красивые async-функции.

Два разных мира асинхронности:


Представьте ситуацию: вы пишете современное приложение на Swift Concurrency, но вам нужно использовать библиотеку или системный API, который работает через completion handlers:

// Старый код (до async/await)
func fetchUserData(userId: Int, completion: @escaping (Result) -> Void) {
// Некоторая асинхронная операция
NetworkService.shared.request(.user(id: userId)) { result in
completion(result)
}
}

// Как мы хотим использовать это в новом коде:
let user = try await fetchUserData(userId: 123)

Решение - Continuations переводчики между эпохами:

Swift предоставляет специальные функции-континуации, которые становятся мостом между двумя моделями:

// Новый async-интерфейс поверх старого API
func fetchUserData(userId: Int) async throws -> User {
return try await withCheckedThrowingContinuation { continuation in
// Вызываем старый метод с completion handler
fetchUserData(userId: userId) { result in
switch result {
case .success(let user):
// Пробуждаем async функцию с результатом
continuation.resume(returning: user)
case .failure(let error):
// Пробуждаем с ошибкой
continuation.resume(throwing: error)
}
}
}
}

Типы континуаций и когда что использовать:


Swift предлагает четыре варианта, каждый для своего случая:

  • withCheckedContinuation - для API, которые всегда возвращают результат.

  • withCheckedThrowingContinuation - для API, которые могут завершиться ошибкой.

  • withUnsafeContinuation и withUnsafeThrowingContinuation - для оптимизации производительности.

Ключевое правило - вызывать resume только один раз:


Самая частая ошибка при работе с континуациями: множественный вызов resume(). Checked-версии помогут отловить это в рантайме:

// Опасный код (может вызвать краш)
func dangerousWrap() async -> String {
await withCheckedContinuation { continuation in
unreliableAPI { value in
continuation.resume(returning: value)
// Если API вызовет completion дважды — будет ошибка
}
}
}

// Безопасная обертка
func safeWrap() async -> String {
await withCheckedContinuation { continuation in
var hasResumed = false
unreliableAPI { value in
guard !hasResumed else { return }
hasResumed = true
continuation.resume(returning: value)
}
}
}

Производительность - Checked vs Unsafe:


  • Checked continuations добавляют runtime-проверки на двойной вызов resume() - безопаснее, но чуть медленнее.

  • Unsafe continuations убирают проверки - быстрее, но требуют абсолютной уверенности в корректности кода.

Вывод:


Continuation - это не просто синтаксический сахар, а мощный инструмент для постепенной миграции на Swift Concurrency. Они открывают возможность интегрировать легаси-код в современную асинхронную архитектуру, создавать удобные async-интерфейсы для API, работающих через колбэки и контролировать процесс миграции, не переписывая все и сразу.
05.12.2025 7 518