Swift中URLSession非Multipart方式文件上传进度获取方法
如何在Swift的URLSession中获取非Multipart方式的文件上传进度?
嗨,我来帮你搞定这个上传进度的问题~你当前用的URLSession.shared.dataTask(with:)确实没法拿到上传进度,因为这个方法是把整个请求体(也就是你的视频数据)先全部加载到内存,再一次性发送,而且它本身没有提供进度回调的接口。要获取实时上传进度,得换个方式——用**URLSession的上传任务(uploadTask)**配合代理方法来实现,具体步骤如下:
核心思路
通过实现URLSessionTaskDelegate协议中的urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)方法,这个方法会在上传数据的过程中被反复调用,里面的参数直接提供了已发送字节数和总字节数,我们可以用它来计算上传进度。
修改后的完整实现
首先让你的类遵守URLSessionTaskDelegate协议(比如放在ViewController或者专门的上传管理类里),然后替换原来的上传代码:
class VideoUploadManager: NSObject, URLSessionTaskDelegate { private var token: String = "你的实际Token" // 替换成你的授权Token func startVideoUpload() { let urlStr = UserDefaults.standard.value(forKey: "Resumable") as? String ?? "" guard let url = URL(string: urlStr) else { print("无效的上传URL") return } do { var request = try URLRequest(url: url, method: .put) // 设置请求头 request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("video/*", forHTTPHeaderField: "Content-Type") request.timeoutInterval = 60.0 // 加载视频文件(推荐用URL方式,避免大文件占用过多内存) guard let videoFileUrl = Bundle.main.url(forResource: "video", withExtension: "mov") else { print("找不到指定的视频文件") return } // 获取文件大小,设置Content-Length(可选,部分服务器会自动处理) let fileSize = try FileManager.default.attributesOfItem(atPath: videoFileUrl.path)[.size] as? Int64 ?? 0 request.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length") // 创建带代理的URLSession let sessionConfig = URLSessionConfiguration.default // 指定代理队列为主线程,方便直接更新UI let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main) // 使用uploadTask从文件上传(大文件推荐这种方式,内存友好) let uploadTask = session.uploadTask(with: request, fromFile: videoFileUrl) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("上传失败:\(error.localizedDescription)") return } guard let httpResponse = response as? HTTPURLResponse else { print("无效的服务器响应") return } print("服务器响应头:\(httpResponse.allHeaderFields)") if httpResponse.statusCode == 200 { if let locationUrl = httpResponse.allHeaderFields["Location"] as? String { print("上传成功!返回的资源URL:\(locationUrl)") } } else { print("上传失败,状态码:\(httpResponse.statusCode)") } } uploadTask.resume() } catch { print("创建请求或读取文件失败:\(error.localizedDescription)") } } // MARK: - URLSessionTaskDelegate func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { // 计算上传进度 let uploadProgress = Double(totalBytesSent) / Double(totalBytesExpectedToSend) // 打印进度,或者更新UI(比如进度条) print("当前上传进度:\(String(format: "%.2f%%", uploadProgress * 100))") // 如果要更新UI,直接在这里操作即可(因为我们指定了代理队列为主线程) // self.progressView.progress = uploadProgress } }
几个关键注意点
- 不要硬编码
Content-Length:用文件的实际大小来设置,避免出现数据长度不匹配导致的上传失败。 - 大文件推荐用
fromFile参数:直接传入文件URL,系统会分块读取文件并上传,不会把整个文件加载到内存,避免内存过高的问题。 - 代理队列的选择:如果需要更新UI,一定要把代理队列设置为主线程(
OperationQueue.main),否则直接操作UI会崩溃;如果不需要更新UI,可以用默认的后台队列。
内容的提问来源于stack exchange,提问作者TechChain
相关产品推荐
相关产品推荐

