Swift中实现出站网络请求限流及视图任务管理方案
解决SwiftUI中带速率限制的网络请求队列问题
核心需求拆解
- 遵守API的1 RPS速率限制,避免触发限流
- 单个请求失败不中断其他请求的执行
- 视图导航时自动取消该视图发起的未完成请求,释放队列资源
解决方案实现
1. 全局速率限制器(RateLimiter)
用Actor实现线程安全的请求队列,支持任务取消和速率控制:
import Foundation import AsyncAlgorithms actor RateLimiter { private let queue = AsyncChannel<(taskID: UUID, operation: @Sendable () async throws -> Void)>() private let interval: Duration = .seconds(1) private var pendingTasks: Set<UUID> = [] init() { Task { await processQueue() } } private func processQueue() async { for await (taskID, operation) in queue { guard pendingTasks.contains(taskID) else { continue } let start = ContinuousClock.now do { try await operation() } catch is CancellationError { print("任务 \(taskID) 已取消") } catch { print("任务 \(taskID) 执行失败: \(error)") } pendingTasks.remove(taskID) let elapsed = ContinuousClock.now - start let remaining = interval - elapsed if remaining > .zero { try? await Task.sleep(for: remaining) } } } /// 提交请求到队列,返回任务ID用于取消 func submit(operation: @Sendable @escaping () async throws -> Void) -> UUID { let taskID = UUID() pendingTasks.insert(taskID) Task { await queue.send((taskID, operation)) } return taskID } /// 取消指定ID的未执行任务 func cancel(taskID: UUID) { pendingTasks.remove(taskID) } }
2. 全局共享实例
在App级别注入全局RateLimiter:
@main struct MyApp: App { @State private let rateLimiter = RateLimiter() var body: some Scene { WindowGroup { ContentView() .environment(rateLimiter) } } }
3. 视图中使用
在视图中发起请求,绑定视图生命周期,自动取消未完成任务:
struct SomeView: View { let id: String @Environment(RateLimiter.self) private var rateLimiter @State private var taskIDs: [UUID] = [] private func loadData() async { // 提交评论请求 let commentTaskID = rateLimiter.submit { try await api.loadUserComments(id) // 成功后更新SwiftData } taskIDs.append(commentTaskID) // 提交用户数据请求 let userDataTaskID = rateLimiter.submit { try await api.loadUserData(id) // 成功后更新SwiftData } taskIDs.append(userDataTaskID) // 提交用户帖子请求 let postsTaskID = rateLimiter.submit { try await api.loadUserPosts(id) // 成功后更新SwiftData } taskIDs.append(postsTaskID) } var body: some View { Text("加载中...") .task(id: id) { await loadData() // 任务结束后清空本地记录(可选) taskIDs.removeAll() } .onDisappear { // 视图消失时取消所有未完成的任务 for taskID in taskIDs { rateLimiter.cancel(taskID: taskID) } } } }
方案优势
- 速率控制:RateLimiter保证请求严格按照1 RPS执行,自动补偿请求执行时间不足1秒的间隔
- 容错性:单个请求失败不会影响其他请求,错误被单独捕获处理
- 生命周期绑定:视图消失时自动取消对应未执行任务,避免无效请求占用队列资源
- 线程安全:Actor确保队列操作和任务管理的线程安全
内容的提问来源于stack exchange,提问作者sbdchd
相关产品推荐
相关产品推荐

