iOS Swift Concurrency 实战:使用 TaskGroup 重构异步网络请求队列

在 iOS 开发中,异步网络请求是常见需求。传统方法(如使用 DispatchGroup)可能导致代码冗余和错误处理复杂。Swift Concurrency 引入了 TaskGroup,它能高效管理多个并发任务,特别适合重构网络请求队列。下面我将逐步解释如何用 TaskGroup 实现这一目标。

1. 问题场景

假设我们有一个应用需要并发获取多个 URL 的数据(例如,从多个 API 端点下载 JSON)。旧方法可能依赖回调或 DispatchGroup,但这容易引发“回调地狱”和线程安全问题。使用 TaskGroup 可以:

  • 简化代码结构。
  • 自动处理并发调度。
  • 支持结构化错误处理。
2. 重构步骤

我们将分四步重构:

  1. 定义异步网络请求函数:创建一个基本的 async 函数执行单个请求。
  2. 使用 TaskGroup 管理并发:通过 withThrowingTaskGroup 创建任务组,添加多个请求任务。
  3. 收集并处理结果:在组内迭代获取结果,处理可能的错误。
  4. 优化与错误处理:添加超时控制或取消逻辑。
3. 完整代码示例

以下是一个实战代码,使用 Swift Concurrency 和 URLSession。代码假设您已导入 Foundation 框架。

// 步骤 1: 定义异步网络请求函数
/// 从指定 URL 异步获取数据
func fetchData(from url: URL) async throws -> Data {
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    return data
}

// 步骤 2: 使用 TaskGroup 重构请求队列
/// 并发获取多个 URL 的数据
func fetchMultipleUrls(urls: [URL]) async throws -> [Data] {
    // 使用 withThrowingTaskGroup 创建任务组,支持错误抛出
    try await withThrowingTaskGroup(of: Data.self) { group in
        // 添加任务到组中:每个 URL 启动一个并发任务
        for url in urls {
            group.addTask {
                try await fetchData(from: url) // 执行单个请求
            }
        }
        
        // 步骤 3: 收集结果
        var results: [Data] = []
        // 迭代组,等待每个任务完成(并发执行,顺序收集)
        for try await data in group {
            results.append(data) // 添加成功返回的数据
        }
        return results
    }
}

// 示例使用
Task {
    do {
        let urls = [
            URL(string: "https://api.example.com/data1")!,
            URL(string: "https://api.example.com/data2")!,
            URL(string: "https://api.example.com/data3")!
        ]
        let dataArray = try await fetchMultipleUrls(urls: urls)
        print("请求成功,数据量: \(dataArray.count)")
        // 这里可以解析数据或更新 UI
    } catch {
        print("请求失败: \(error.localizedDescription)")
        // 处理错误,例如显示用户提示
    }
}

4. 关键点解释
  • 并发执行group.addTask 会将每个请求添加到任务组,Swift 运行时自动调度它们并发执行(无需手动管理线程)。
  • 错误处理withThrowingTaskGroup 捕获组内任何任务的错误。如果单个请求失败,整个组会抛出错误,确保安全。
  • 结果收集for try await data in group 循环等待所有任务完成,结果按完成顺序返回(但执行是并发的)。
  • 性能优势:相比串行请求,TaskGroup 显著减少总等待时间。例如,3 个请求各需 1 秒,串行需 3 秒,TaskGroup 并发可能只需 1 秒左右。
5. 优化建议
  • 添加超时控制:在 group.addTask 内使用 Task.sleepwithTimeout(Swift 5.7+ 支持)防止长时间阻塞。
    group.addTask {
        try await Task.sleep(nanoseconds: 5_000_000_000) // 5 秒超时
        return try await fetchData(from: url)
    }
    

  • 取消任务:调用 group.cancelAll() 在需要时(如用户退出页面)取消所有请求。
  • 限制并发数:TaskGroup 默认无上限,但可通过逻辑控制(如使用 AsyncSequence 分批添加任务)避免资源耗尽。
  • UI 更新:在 Task 主线程中更新 UI,因为网络请求在后台线程运行。
6. 优点总结

通过 TaskGroup 重构:

  • 代码简洁:减少嵌套回调,代码更易读和维护。
  • 安全可靠:自动处理线程安全和错误传播。
  • 高效并发:最大化利用设备资源,提升应用性能。

在实际项目中,您可以根据需求扩展此模式(例如添加重试逻辑或进度跟踪)。Swift Concurrency 的 TaskGroup 是处理异步队列的强大工具,推荐在 Xcode 13+ 中实践。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐