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, работающих через колбэки и контролировать процесс миграции, не переписывая все и сразу.