iOS关于大文件上传二

185 阅读2分钟

第一种上传方案需要对大文件进行分割处理,其实从某种程度上会提高应用的内存使用,如果进行多个较大文件的上传工作可能会造成内存紧张,是有一些弊端存在的,所以从第一种方案的角度进行优化,尝试第二种方案进行上传,不对文件进行分割直接从文件中进行读取指定偏移量位置,并实时记录上传偏移量的位置,显示上传进度。

上传操作类,自定义封装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 实例,并上传指定文件。