第一种上传方案需要对大文件进行分割处理,其实从某种程度上会提高应用的内存使用,如果进行多个较大文件的上传工作可能会造成内存紧张,是有一些弊端存在的,所以从第一种方案的角度进行优化,尝试第二种方案进行上传,不对文件进行分割直接从文件中进行读取指定偏移量位置,并实时记录上传偏移量的位置,显示上传进度。
上传操作类,自定义封装Operation类,负责上传单独的数据块工作
class UploadOperation: Operation {
private let data: Data
private let serverURL: URL
private let offset: Int
init(data: Data, serverURL: URL, offset: Int) {
self.data = data
self.serverURL = serverURL
self.offset = offset
}
override func main() {
if isCancelled { return }
var request = URLRequest(url: serverURL)
request.httpMethod = "POST"
request.addValue("bytes \(offset)-\(offset + data.count - 1)", forHTTPHeaderField: "Content-Range")
request.httpBody = data
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: request) { _, _, error in
if let error = error {
print("Upload error: \(error)")
} else {
print("Uploaded chunk starting at \(self.offset)")
}
semaphore.signal()
}.resume()
semaphore.wait()
}
}
文件上传管理类
import Foundation
class FileUploader {
private let chunkSize = 1024 * 1024 // 1MB
private let maxConcurrentUploads = 4
private let uploadQueue = OperationQueue()
private var uploadProgress: [URL: Int] = [:]
private let progressFileURL: URL
init() {
uploadQueue.maxConcurrentOperationCount = maxConcurrentUploads
progressFileURL = FileManager.default.temporaryDirectory.appendingPathComponent("uploadProgress.plist")
loadProgress()
}
func uploadFile(url: URL, serverURL: URL, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let fileData = try Data(contentsOf: url)
let fileSize = fileData.count
let chunkCount = (fileSize + chunkSize - 1) / chunkSize
let lastUploadedOffset = uploadProgress[url] ?? 0
for chunkIndex in lastUploadedOffset..<chunkCount {
let start = chunkIndex * chunkSize
let end = min(start + chunkSize, fileSize)
let chunkData = fileData.subdata(in: start..<end)
let uploadOperation = UploadOperation(data: chunkData, serverURL: serverURL, offset: start)
uploadOperation.completionBlock = {
if uploadOperation.isCancelled {
completion(.failure(NSError(domain: "UploadCancelled", code: -1, userInfo: nil)))
return
}
self.uploadProgress[url] = chunkIndex + 1
self.saveProgress()
if chunkIndex == chunkCount - 1 {
completion(.success(()))
}
}
uploadQueue.addOperation(uploadOperation)
}
} catch {
completion(.failure(error))
}
}
func cancelAllUploads() {
uploadQueue.cancelAllOperations()
}
private func loadProgress() {
if let progress = NSDictionary(contentsOf: progressFileURL) as? [URL: Int] {
uploadProgress = progress
}
}
private func saveProgress() {
(uploadProgress as NSDictionary).write(to: progressFileURL, atomically: true)
}
}
使用示例
let fileUploader = FileUploader()
let fileURL = Bundle.main.url(forResource: "largefile", withExtension: "dat")!
let serverURL = URL(string: "https://yourserver.com/upload")!
fileUploader.uploadFile(url: fileURL, serverURL: serverURL) { result in
switch result {
case .success():
print("Upload successful")
case .failure(let error):
print("Upload failed: \(error)")
}
}
}
}
-
FileUploader 类初始化:配置了上传队列的最大并发数,并加载了之前保存的上传进度。
-
loadProgress 方法:从文件中加载已保存的上传进度。
-
saveProgress 方法:将当前的上传进度保存到文件中。
-
uploadFile 方法:开始上传文件。打开文件句柄,计算总分片数和已上传的分片数,然后依次上传每个分片。
-
示例代码:创建
FileUploader实例,并上传指定文件。