You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将带完成回调的后台请求逻辑转为Async/Await实现?

问题:将带后台回调缓存的请求逻辑转为Async/Await形式

我在NetworkManager类中有一个通过完成回调返回Bar对象的getBar方法。当应用处于后台时,我会把请求和回调存入FooManager,等应用回到前台时重新发起请求并完成原回调,逻辑如下:

// 原基于回调的实现
func getBar(
  withRequestId requestId: String,
  completion: (Bar) -> Void
) {
  let applicationState = UIApplication.shared.applicationState
  if applicationState == .background {
    let request = BarRequest(
      id: requestId,
      completion: completion
    )
    fooManager.request = request
    return
  }
  // 前台时直接执行请求逻辑...
}

当FooManager收到applicationDidBecomeActive通知时,重新调用请求并触发原回调:

func applicationDidBecomeActive() {
  guard let request else {
    return
  }
  network.getBar(
    withRequestId: request.id,
    completion: request.completion
  )
  self.request = nil
}

现在要把getBar转为async throws形式,但因为没有可直接保存的回调,不知道怎么实现等价逻辑:

// 待实现的Async/Await版本
func getBar(withRequestId requestId: String) async throws -> Bar {
  let applicationState = UIApplication.shared.applicationState
  if applicationState == .background {
    let configRequest = ConfigRequest(
      id: requestId,
      completion: completion // 此处无法直接获取回调
    )
    configManager.configRequest = configRequest
    return
  }
  // 前台时的异步请求逻辑...
}

解决方案:利用Continuation保存异步延续

在Async/Await模式下,可以通过CheckedThrowingContinuation(对应throws场景)捕获异步函数的执行延续,把它和请求信息一起保存,等应用回到前台时再恢复执行。

步骤1:定义包含Continuation的请求结构体

修改保存请求的结构体,用CheckedThrowingContinuation替代原回调:

struct PendingBarRequest {
    let requestId: String
    let continuation: CheckedThrowingContinuation<Bar, Error>
}

步骤2:修改Async版本的getBar方法

在getBar中,当应用处于后台时,通过withCheckedThrowingContinuation获取延续,将其存入FooManager并挂起函数;前台时直接执行异步请求逻辑:

func getBar(withRequestId requestId: String) async throws -> Bar {
    let applicationState = UIApplication.shared.applicationState
    
    if applicationState == .background {
        return try await withCheckedThrowingContinuation { continuation in
            // 创建包含延续的待处理请求
            let pendingRequest = PendingBarRequest(
                requestId: requestId,
                continuation: continuation
            )
            // 存入FooManager
            fooManager.pendingRequest = pendingRequest
        }
    }
    
    // 前台时直接执行异步请求逻辑(示例)
    return try await performBarRequest(with: requestId)
}

// 实际发起请求的异步方法(示例)
private func performBarRequest(with requestId: String) async throws -> Bar {
    // 这里写真实的请求逻辑,比如网络请求、本地读取等
}

步骤3:修改FooManager的前台恢复逻辑

当应用回到前台时,取出保存的PendingBarRequest,调用异步版本的getBar,拿到结果后恢复延续,或捕获错误并传递:

class FooManager {
    var pendingRequest: PendingBarRequest?
    
    func applicationDidBecomeActive() {
        guard let pendingRequest = pendingRequest else { return }
        
        // 异步发起请求,避免阻塞主线程
        Task {
            do {
                let bar = try await network.getBar(withRequestId: pendingRequest.requestId)
                // 成功时恢复延续,返回结果
                pendingRequest.continuation.resume(returning: bar)
            } catch {
                // 失败时恢复延续,抛出错误
                pendingRequest.continuation.resume(throwing: error)
            }
            // 清空待处理请求
            self.pendingRequest = nil
        }
    }
}

注意事项

  • 必须确保continuation被恰好调用一次,否则会导致运行时崩溃或内存泄漏。即使应用被终止,也要考虑在applicationWillTerminate中用合适的错误恢复未完成的延续。
  • 如果需要支持多个待处理请求,可以把pendingRequest改成数组,批量处理。
  • CheckedThrowingContinuation会做运行时检查,确保延续只被调用一次,开发阶段能快速发现问题;如果追求性能,也可以用UnsafeContinuation,但需要自己保证调用的安全性。

内容的提问来源于stack exchange,提问作者Tometoyou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 12:55:25