Swift Academy · 课时

将旧式回调/Combine 接入 async/await

使用 withCheckedContinuation / withCheckedThrowingContinuation 包装完成处理器,并将 Combine 发布者接入 AsyncSequence ,以支持简单的 for await 循环。

第 2 / 3 课8 个步骤

将旧式回调/Combine 接入 async/await 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 2 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 3 节课。

为什么要桥接?

目标:使用延续包装回调,并将 Combine 流作为 AsyncSequence 处理。

  • 延续(不抛出错误/抛出错误)
  • URLSession 桥接示例
  • Combine → for await 消费

不抛出错误的延续

当旧式 API 不会失败时,请使用 withCheckedContinuation。仅调用一次 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,通过 throw 暴露旧式 API 的错误。确保只有一条调用 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) }
}

将 Combine 用作 AsyncSequence

许多 Combine 发布者会暴露 .values,让您可以对发出的值使用 for await,而无需自定义桥接。

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)

发布者 → 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
}

使用抛出错误的延续

快速检查:将可能失败的回调桥接到异步代码的最佳方式是什么?

回顾

回顾:使用延续(抛出错误或不抛出错误)包装旧式回调,并使用 .values 或小型 AsyncStream 桥接来消费 Combine。确保 resume 只能调用一次,并在完成时取消订阅。

免费开始

用 AI 导师学习 Swift — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
122
课程
409

常见问题解答

「将旧式回调/Combine 接入 async/await」课时是免费的吗?

是的 — 「将旧式回调/Combine 接入 async/await」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 3 节课。

「将旧式回调/Combine 接入 async/await」这节课中我会学到什么?

使用 withCheckedContinuation / withCheckedThrowingContinuation 包装完成处理器,并将 Combine 发布者接入 AsyncSequence ,以支持简单的 for await 循环。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Swift Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 3 节。

「将旧式回调/Combine 接入 async/await」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Swift Academy 课中编写并运行代码吗?

能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 生产者/消费者、流水线与超时
  2. 将旧式回调/Combine 接入 async/await
  3. 测试异步代码(XCTest)
← 返回 Swift Academy