Переход от устаревших обратных вызовов и Combine к async/await
Оберните обработчики завершения с помощью withCheckedContinuation / withCheckedThrowingContinuation и преобразуйте издателей Combine в AsyncSequence для простых циклов for await .
«Переход от устаревших обратных вызовов и Combine к async/await» — бесплатный урок Swift Academy на CoddyKit. Это урок 2 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 3 уроков всего.
Зачем выполнять интеграцию?
Цель: используйте продолжения, чтобы оборачивать обратные вызовы и работать с потоками Combine как с AsyncSequence.
- Продолжения (с выбрасыванием ошибок и без него)
- Пример интеграции с URLSession
- Потребление Combine через
for await
Продолжение без ошибок
Используйте withCheckedContinuation, если старый API не может завершиться с ошибкой. Выполняйте resume ровно один раз.
// Old-style API
func fetchName(completion: @escaping (String) -> Void) {
// simulate async
Task { try? await Task.sleep(nanoseconds: 80_000_000); completion("Ana") }
}
// Async wrapper using continuation (non-throwing)
func fetchName() async -> String {
await withCheckedContinuation { (cont: CheckedContinuation<String, Never>) in
fetchName { value in cont.resume(returning: value) }
}
}
Task {
let name = await fetchName()
print("name:", name) // "Ana"
}Продолжение с ошибками
Используйте withCheckedThrowingContinuation, чтобы передавать ошибки старого API через throw. Обеспечьте единственный путь выполнения resume.
enum NetError: Error { case offline; case badStatus }
func loadNumber(completion: @escaping (Result<Int, Error>) -> Void) {
Task {
try? await Task.sleep(nanoseconds: 80_000_000)
completion(.failure(NetError.offline))
}
}
func loadNumber() async throws -> Int {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Int, Error>) in
loadNumber { result in
switch result {
case .success(let v): cont.resume(returning: v)
case .failure(let e): cont.resume(throwing: e)
}
}
}
}
Task {
do {
let n = try await loadNumber()
print(n)
} catch {
print("error:", error) // offline
}
}Интеграция с URLSession
Оберните API с обработчиками завершения, например URLSession, чтобы получить понятную асинхронную функцию с выбрасыванием ошибок. Не забывайте выполнять resume ровно один раз.
import Foundation
func getText(from url: URL) async throws -> String {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<String, Error>) in
let task = URLSession.shared.dataTask(with: url) { data, resp, err in
if let err = err { cont.resume(throwing: err); return }
guard let data = data, let text = String(data: data, encoding: .utf8) else {
cont.resume(throwing: URLError(.badServerResponse)); return
}
cont.resume(returning: text)
}
task.resume()
}
}
// Demo (works in environments with network access)
let demoURL = URL(string: "https://example.com")!
Task {
do { let body = try await getText(from: demoURL); print(body.prefix(15)) }
catch { print("network error:", error) }
}Publisher как AsyncSequence
Многие издатели Combine предоставляют .values, позволяя перебирать асинхронно выдаваемые значения без специальных интеграционных обёрток.
import Combine
import Foundation
let subject = PassthroughSubject<Int, Never>()
// Consume Combine as AsyncSequence:
Task {
var produced: [Int] = []
for await value in subject.values { // Publisher.values -> AsyncSequence
produced.append(value)
if produced.count == 3 { break } // stop after 3 values
}
print("collected:", produced) // e.g., [1,2,3]
}
// Publish a few values
var cancellables = Set<AnyCancellable>()
Timer.publish(every: 0.05, on: .main, in: .common)
.autoconnect()
.prefix(3)
.scan(0) { acc, _ in acc + 1 }
.sink { subject.send($0) }
.store(in: &cancellables)Publisher → AsyncStream
Если .values недоступен, создайте небольшую интеграцию через AsyncStream. Не забудьте выполнить cancel подписки после завершения.
import Combine
// Bridge ANY Publisher to AsyncSequence using AsyncStream (simple version)
extension Publisher {
func asAsyncStream() -> AsyncStream<Output> {
AsyncStream { continuation in
let cancel = self.sink(
receiveCompletion: { _ in continuation.finish() },
receiveValue: { value in continuation.yield(value) }
)
continuation.onTermination = { _ in cancel.cancel() }
}
}
}
// Demo:
let pub = [10, 20, 30].publisher
Task {
var sum = 0
for await v in pub.asAsyncStream() { sum += v }
print("sum:", sum) // 60
}Используйте продолжение с ошибками
Быстрая проверка: как лучше всего связать обратный вызов, который может завершиться с ошибкой, с асинхронным кодом?
Итоги
Итоги: оборачивайте старые обратные вызовы с помощью продолжений (с выбрасыванием ошибок или без него), а Combine используйте через .values или небольшую интеграцию с AsyncStream. Выполняйте resume только один раз и вызывайте cancel для подписок после завершения.
Часто задаваемые вопросы
Урок «Переход от устаревших обратных вызовов и Combine к async/await» бесплатный?
Да — полный текст урока «Переход от устаревших обратных вызовов и Combine к async/await» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 3 уроков всего.
Чему я научусь в уроке «Переход от устаревших обратных вызовов и Combine к async/await»?
Оберните обработчики завершения с помощью withCheckedContinuation / withCheckedThrowingContinuation и преобразуйте издателей Combine в AsyncSequence для простых циклов for await . Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Swift Academy?
Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 3.
Сколько времени занимает урок «Переход от устаревших обратных вызовов и Combine к async/await»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Swift Academy?
Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Производитель и потребитель, конвейеры, тайм-ауты
- Переход от устаревших обратных вызовов и Combine к async/await
- Тестирование асинхронного кода (XCTest)