Highlight
iOS 15 为 URLSession 新增了
data(for:)、upload(for:from:)、download(from:)、bytes(from:)等原生 async 方法,支持通过 task-specific delegate 处理认证挑战,同时引入 AsyncSequence 用于增量接收响应体。
核心内容
为什么要引入 async/await 版本的 URLSession?
一个常见的网络请求代码,使用 completion handler 存在三个问题:
- 控制流跳来跳去:创建 task → resume → 跳到 completionHandler → 跳到主队列的 completionHandler
- 线程安全陷阱:涉及三个执行上下文(调用者队列、session delegate 队列、主队列),编译器无法帮助检测数据竞争
- 错误处理困难:completionHandler 可能被调用两次,
UIImage(data:)返回 optional 但没有对应的 error
改成 async/await 后,控制流从上到下线性执行,所有代码在同一并发上下文中运行,不需要担心线程问题,错误用 throw 处理,编译器会强制处理 optional。
URLSession 的 async 方法
iOS 15 和 macOS Monterey 新增的 async 方法包括:
data(from:)/data(for:):获取数据,等效于 data task convenience 方法upload(for:from:):上传数据或文件,等效于 upload taskdownload(from:)/download(for:):下载文件到磁盘,不自动删除,需要手动移动或删除bytes(from:)/bytes(for:):返回AsyncBytes,用于增量接收响应体
所有方法都支持 Swift 原生的 try-catch 错误处理和 Task 取消。
处理增量响应:URLSession.bytes
data、upload、download 方法都等待完整响应体到达后才返回。如果需要增量处理(如 SSE、流式 JSON),使用 bytes 方法。
bytes(from:) 在响应头到达后立即返回,响应体以 AsyncSequence<UInt8> 形式增量交付。AsyncBytes 提供 lines 转换,按行解析响应体。
Task-specific Delegate
URLSession 的 delegate 模型提供了认证挑战、metrics 等回调。新的 async 方法不再暴露底层 task,如何处理特定任务的认证挑战?
答案是为 async 方法传入一个 task-specific delegate 参数。这个 delegate 只处理该特定 task 的事件,且被 task 强引用直到完成或失败。
如果 session delegate 和 task delegate 都实现了同一个方法,task delegate 的实现优先。注意:Background URL Session 不支持 task-specific delegate。
Swift Concurrency 取消机制
Task 类型有 cancel() 方法,但注意 Swift Concurrency 的 Task 和 URLSessionTask 是完全不同的概念,只是名字相似。
取消是协作式的——调用 cancel() 后,代码在下一个 suspend point(如 await)检查取消状态并抛出 CancellationError。
详细内容
获取照片的 async 版本
(02:52)
func fetchPhoto(url: URL) async throws -> UIImage
{
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw WoofError.invalidServerResponse
}
guard let image = UIImage(data: data) else {
throw WoofError.unsupportedImage
}
return image
}
关键点:
data(from:)返回(Data, URLResponse)元组- 用
guard检查状态码,不符合则throw错误 UIImage(data:)返回 optional,必须处理nil情况- 整个函数是
async throws,调用时用try await
URL Resquest 版本的 data 方法
(03:45)
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw MyNetworkingError.invalidServerResponse
}
关键点:
data(for:)接受URLRequest参数,可以配置 HTTP 头、方法等- 响应检查模式相同:guard cast + 检查状态码
上传文件
(04:03)
var request = URLRequest(url: url)
request.httpMethod = "POST"
let (data, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 201 else {
throw MyNetworkingError.invalidServerResponse
}
关键点:
upload(for:fromFile:)从磁盘文件上传,适合大文件- 上传成功通常返回 201 Created,检查对应的状态码
- 也有
upload(for:from:)版本,从Data上传
下载文件
(04:21)
let (location, response) = try await URLSession.shared.download(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw MyNetworkingError.invalidServerResponse
}
try FileManager.default.moveItem(at: location, to: newLocation)
关键点:
download(from:)返回临时文件 URL,需要手动移动到目标位置- 旧版 convenience 方法会自动删除文件,新版不会
- 务必在处理完成后删除或移动临时文件
任务取消
(04:44)
let task = Task {
let (data1, response1) = try await URLSession.shared.data(from: url1)
let (data2, response2) = try await URLSession.shared.data(from: url2)
}
task.cancel()
关键点:
Task { }创建 structured concurrency 任务,返回Task.Handlecancel()在下一个await点生效,抛出CancellationError- 如果第一个
await已经返回,取消不会影响第二个请求,两个串行请求都会完成
增量接收响应体
(07:53)
let (bytes, response) = try await URLSession.shared.bytes(from: Self.eventStreamURL)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw WoofError.invalidServerResponse
}
for try await line in bytes.lines {
let photoMetadata = try JSONDecoder().decode(PhotoMetadata.self, from: Data(line.utf8))
await updateFavoriteCount(with: photoMetadata)
}
关键点:
bytes(from:)返回(URLSession.AsyncBytes, URLResponse)元组AsyncBytes.lines按\n分割响应体,返回AsyncSequence<String>for try await循环中处理每一行,适合 SSE、流式 JSON 场景await updateFavoriteCount调用另一个 async 函数更新 UI
Task-specific Delegate 处理认证
(11:20)
class AuthenticationDelegate: NSObject, URLSessionTaskDelegate {
private let signInController: SignInController
init(signInController: SignInController) {
self.signInController = signInController
}
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge
) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
do {
let (username, password) = try await signInController.promptForCredential()
return (.useCredential,
URLCredential(user: username, password: password, persistence: .forSession))
} catch {
return (.cancelAuthenticationChallenge, nil)
}
} else {
return (.performDefaultHandling, nil)
}
}
}
使用方式:
let delegate = AuthenticationDelegate(signInController: signInController)
let (data, response) = try await URLSession.shared.data(for: request, delegate: delegate)
关键点:
- delegate 方法本身可以是
async,支持 await 调用 - delegate 被 task 强引用,不需要手动持有
- 对于 HTTP Basic 认证,可以弹出登录框获取凭据
- 返回
(useCredential, credential)告诉 session 使用凭据继续请求
核心启发
-
将现有网络层迁移到 async/await
- 做什么:把所有基于 completion handler 的网络请求方法改成 async throws 版本
- 为什么值得做:代码可读性大幅提升,错误处理更安全,减少线程安全 bug
- 怎么开始:从最常用的请求方法开始,把
completionHandler: @escaping (Result<T, Error>) -> Void改成async throws -> T。入口 API 是URLSession.data(for:)
-
为实时更新接口接入 bytes API
- 做什么:用
URLSession.bytes(from:)替代轮询或 WebSocket 处理 SSE/流式接口 - 为什么值得做:增量接收响应体,内存占用更低,延迟更小
- 怎么开始:检查现有 SSE 接口,用
bytes.lines按行解析,在for try await循环中处理每条消息
- 做什么:用
-
用 task-specific delegate 简化认证逻辑
- 做什么:为需要特殊认证的请求创建专用 delegate 类,而不是在全局 delegate 中用 task ID 分发
- 为什么值得做:delegate 逻辑与请求逻辑放在一起,更易维护
- 怎么开始:实现
URLSessionTaskDelegate,在data(for:delegate:)中传入实例。delegate 方法可以标记为async
-
优化文件下载的内存占用
- 做什么:对大文件下载使用
download(from:)而不是data(from:) - 为什么值得做:
data将整个响应体加载到内存,download写入临时文件,内存占用恒定 - 怎么开始:将所有图片、视频下载改为
download,下载完成后用FileManager.moveItem移动到目标位置
- 做什么:对大文件下载使用
关联 Session
- Meet async/await in Swift — Swift async/await 入门
- Accelerate networking with HTTP/3 and QUIC — HTTP/3 和 QUIC 协议的深入讲解
- Meet AsyncSequence — AsyncSequence 的使用方法
- Protect mutable state with Swift actors — Swift Actor 的并发安全机制
评论
GitHub Issues · utterances