Highlight
URLSession 在 iOS 17 中为上传任务(Upload Task)新增了与下载任务(Download Task)一致的暂停/恢复 API,并引入了基于 IETF 标准的可恢复上传协议,同时新增了后台传输的精细化控制能力。
核心内容
开发一个需要传输大文件的应用时,网络中断是常态而非意外。用户进电梯、切换 Wi-Fi、走到信号盲区,正在进行的几 GB 下载或上传就可能中断,需要从头再来。iOS 17 之前的 URLSession 对下载任务有 cancelByProducingResumeData 这套暂停恢复机制,但上传任务没有对等的 API。如果你想做一个能断点续传的文件上传功能,只能自己在应用层实现分片逻辑。
iOS 17 给上传任务补齐了这块短板。URLSessionUploadTask 现在支持 cancelByProducingResumeData() 暂停上传并生成恢复数据,也支持 uploadTask(withResumeData:) 从恢复点继续。API 形状与下载任务完全一致,学习成本为零。
但光有客户端 API 还不够。暂停恢复机制需要服务器配合——服务器必须理解客户端从哪里继续上传。iOS 17 引入了基于最新 IETF 标准草案的可恢复上传协议(Resumable Uploads Protocol)。Apple 还发布了 SwiftNIO 的服务端参考实现,方便后端团队快速对接。
此外,Background URLSession 在 iOS 17 中获得了更细粒度的控制:可以设置任务的最早开始时间、预估传输字节数以优化系统调度,以及声明是否允许使用受限网络。
详细内容
下载任务的暂停与恢复(04:53)
URLSession 的下载任务暂停恢复机制基于 HTTP Range 请求。服务器通过 Accept-Ranges 响应头声明支持范围请求,通过 ETag 标识资源版本。中断后客户端发送 Range 请求并附带 If-Range 头,确保资源未被修改。
创建下载任务后,用 cancelByProducingResumeData() 替代普通的 cancel():
let downloadTask = session.downloadTask(with: request)
downloadTask.resume()
guard let resumeData = await downloadTask.cancelByProducingResumeData() else {
// Download cannot be resumed
return
}
恢复数据(resume data)是一个二进制 blob,包含了已下载的字节、原始请求和服务器验证信息。用它创建新的下载任务即可从断点继续:
let downloadTask = session.downloadTask(with: request)
downloadTask.resume()
guard let resumeData = await downloadTask.cancelByProducingResumeData() else {
// Download cannot be resumed
return
}
let newDownloadTask = session.downloadTask(withResumeData: resumeData)
newDownloadTask.resume()
关键点:
cancelByProducingResumeData()是异步方法,会等待系统生成恢复数据后再返回- 恢复数据不可序列化到本地——它可能包含临时文件路径,应用重启后失效
- 返回
nil表示服务器不支持断点续传,此时只能从头下载
如果下载因网络错误而意外中断,可以从 URLError 中提取恢复数据(06:34):
do {
let (url, response) = try await session.download(for: request)
} catch let error as URLError {
guard let resumeData = error.downloadTaskResumeData else {
// Download cannot be resumed
return
}
}
关键点:
URLError.downloadTaskResumeData只在下载任务的错误中包含- 处理错误时先检查恢复数据是否存在,再决定是否从头重试
上传任务的暂停与恢复(08:29)
iOS 17 之前,上传任务只能用 cancel() 彻底取消,没有任何恢复机制。新 API 与下载任务完全对称。
暂停上传:
let uploadTask = session.uploadTask(with: request, fromFile: fileURL)
uploadTask.resume()
guard let resumeData = await uploadTask.cancelByProducingResumeData() else {
// Upload cannot be resumed
return
}
恢复上传:
let uploadTask = session.uploadTask(with: request, fromFile: fileURL)
uploadTask.resume()
guard let resumeData = await uploadTask.cancelByProducingResumeData() else {
// Upload cannot be resumed
return
}
let newUploadTask = session.uploadTask(withResumeData: resumeData)
newUploadTask.resume()
从错误中恢复上传(09:22):
do {
let (data, response) = try await session.upload(for: request, fromFile: fileURL)
} catch let error as URLError {
guard let resumeData = error.uploadTaskResumeData else {
// Upload cannot be resumed
return
}
}
关键点:
- 上传恢复数据与下载恢复数据是不同类型,不能混用
- 如果只是短暂中断且服务器可达,URLSession 会自动尝试恢复上传,无需手动处理
- 上传恢复需要服务器支持最新的 IETF 可恢复上传协议草案
可恢复上传协议与 SwiftNIO 服务端实现(13:15)
客户端暂停上传后,恢复时向服务器发送一个特殊的 HTTP 请求,询问”我已经上传了多少字节,从哪里继续”。服务器根据之前接收的数据返回偏移量,客户端从该偏移量继续发送。
这套协议需要服务器端配合实现。Apple 在 SwiftNIO 中提供了开箱即用的 NIOResumableUpload 模块。
没有可恢复上传支持的 SwiftNIO 服务端代码:
NIOTSListenerBootstrap(group: NIOTSEventLoopGroup())
.childChannelInitializer { channel in
channel.configureHTTP2Pipeline(mode: .server) { channel in
channel.pipeline.addHandlers([
HTTP2FramePayloadToHTTPServerCodec(),
ExampleChannelHandler()
])
}.map { _ in () }
}
.tlsOptions(tlsOptions)
添加可恢复上传支持后(14:06):
import NIOResumableUpload
let uploadContext = HTTPResumableUploadContext(origin: "https://example.com")
NIOTSListenerBootstrap(group: NIOTSEventLoopGroup())
.childChannelInitializer { channel in
channel.configureHTTP2Pipeline(mode: .server) { channel in
channel.pipeline.addHandlers([
HTTP2FramePayloadToHTTPServerCodec(),
HTTPResumableUploadHandler(context: uploadContext, handlers: [
ExampleChannelHandler()
])
])
}.map { _ in () }
}
.tlsOptions(tlsOptions)
关键点:
HTTPResumableUploadContext用origin参数声明允许恢复上传的来源域名HTTPResumableUploadHandler包裹在原有的业务 handler 外层,自动处理协议握手- 该模块基于 HTTP/2,需要先配置 HTTP/2 pipeline
NIOResumableUpload是独立的 Swift package,需要单独引入
信息响应代理方法(15:48)
可恢复上传协议依赖 HTTP 信息响应(1xx 状态码)来协商恢复参数。iOS 17 新增了 URLSessionTaskDelegate 的方法来接收这些响应:
protocol URLSessionTaskDelegate : URLSessionDelegate {
optional func urlSession(_ session: URLSession, task: URLSessionTask,
didReceiveInformationalResponse response: HTTPURLResponse)
}
关键点:
- 信息响应(如
100 Continue、104 Upload Resumption)不会被视作最终响应 - 这个方法在收到每个信息响应时调用一次
- 对于可恢复上传,系统内部使用此机制处理协议协商,通常不需要手动干预
Background URLSession 最佳实践(18:19)
Background URLSession 让传输任务在应用被挂起后仍能继续运行。iOS 17 增加了更精细的控制选项:
// Configuring your background session
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app")
configuration.isDiscretionary = true
configuration.allowsConstrainedNetworkAccess = false
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
// Configuring your background task
let backgroundTask = session.uploadTask(with: url, fromFile: fileURL)
backgroundTask.earliestBeginDate = .now.addingTimeInterval(60 * 60)
backgroundTask.countOfBytesClientExpectsToSend = 500 * 1024
backgroundTask.countOfBytesClientExpectsToReceive = 200
关键点:
isDiscretionary = true:系统在 Wi-Fi 且电量充足时才执行传输,适合非紧急内容allowsConstrainedNetworkAccess = false:禁止使用低数据模式或热点等受限网络earliestBeginDate:设置任务的最早开始时间,例如推迟到凌晨低峰期执行countOfBytesClientExpectsToSend/Receive:预估传输字节数,帮助系统优化调度和电量消耗- Background URLSession 的 delegate 必须设置,否则无法接收完成回调
核心启发
1. 做一个带进度条的断点续传下载管理器
URLSession 的下载暂停恢复 API 已经足够成熟,结合 URLSessionDownloadDelegate 的进度回调,可以封装一个 ResumableDownloadManager,管理多个下载任务的状态(下载中/已暂停/已完成)、进度和恢复数据。用户手动暂停时调用 cancelByProducingResumeData(),将恢复数据存到本地(注意处理临时文件路径失效问题),下次启动时重建任务。
2. 为社交应用添加大视频断点上传
利用新的上传恢复 API,做一个类似微信朋友圈的视频上传体验:用户选好视频后立即开始上传,退出应用或切到后台时自动暂停并保存恢复数据,下次打开自动继续。关键点是恢复数据的生命周期管理——需要将恢复数据序列化到磁盘,并在恢复后清理。
3. 为后端团队提供 SwiftNIO 可恢复上传接入方案
如果团队使用 Swift 后端(Vapor 或裸 SwiftNIO),可以直接引用 NIOResumableUpload 模块,按照 session 中的代码模板接入。关键配置是 HTTPResumableUploadContext 的 origin 参数和 pipeline handler 的顺序。客户端无需额外代码——URLSession 的上传恢复 API 自动使用标准协议。
4. 后台内容预加载系统
结合 Background URLSession 的 earliestBeginDate 和 isDiscretionary 配置,做一个新闻或视频应用的离线预加载功能。在应用前台时,为每个待预加载的资源创建后台任务,设置凌晨时段执行。系统会在 Wi-Fi + 充电条件下自动完成下载,用户早晨打开应用时内容已就绪。
关联 Session
- Ready, set, relay: Protect app traffic with network relays — 了解如何用 relay 保护应用的网络流量隐私
- Reduce network delays with L4S — 了解 L4S 如何降低网络延迟,提升实时应用的响应速度
- What’s new in Background Assets — 了解 Background Assets 如何在应用启动前预下载内容
评论
GitHub Issues · utterances