0Pricing
Swift Academy · Урок

Переход от устаревших обратных вызовов и 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 — локальная установка не требуется.

Все уроки этого курса

  1. Производитель и потребитель, конвейеры, тайм-ауты
  2. Переход от устаревших обратных вызовов и Combine к async/await
  3. Тестирование асинхронного кода (XCTest)
← Назад к Swift Academy