使用完成处理程序的异步调用上的多个 URLSession dataTask 导致内存增加

Multiple URLSession dataTask on asynchronous call with completion handler causes memory goes up

我正在 swift 开发上传项目。我正在使用 imagepickercontroller 拍摄非常大的文件(视频、大小超过 500 MB 的图片)并将该文件分成大小为 1 MB 的块。然后我将这些块发送到远程服务器并在服务器中对它们进行碎片整理,然后我将这个文件显示给用户。

如果文件大小小于 300 MB,我没有问题。但是在这个大小之后,内存上升太多并且应用程序崩溃了。实际上,在每种情况下,内存使用量都在增加,但没有崩溃。

当我在控制台上查看进度时,我看到 URLSession 任务开始了。但是,由于这些任务正在等待来自完成处理程序的响应,任务队列正在增长并且内存使用量上升。有没有办法在任务开始时,该任务的完成处理程序也开始?我想如果我可以同时释放任务队列,我的问题就解决了。我在等你的帮助。

let url:URL = URL(string: "\(addressPrefix)UploadFile")!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
request.httpMethod = "POST"

let bodyData = "\(metaDataID)~\(chunkIndex)~\(chunkSize)~\(chunkHash)~\(wholeTicket)~\(fileDataString)"

request.httpBody = bodyData.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue));
request.timeoutInterval = .infinity

let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
    guard let _:Data = data, let _:URLResponse = response, error == nil else {
       var attemptCounter = 1
       if attemptCounter <= 3 {
            completion("\(attemptCounter).attempt",chunkSize, error)
            attemptCounter += 1
        }
         return
     }
    let jsonStr = String(data: data!, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
    completion(jsonStr, chunkSize, error) 
 SingletonConnectionManager.sharedConnectionDataManager.dataTasks["uploadFile"] = nil 
})  
SingletonConnectionManager.sharedConnectionDataManager.dataTasks["uploadFile"] = task 
task.resume()

---我在表视图控制器中从这个函数调用这个 URLSession 任务

 tmpConnection.uploadFile(chunk, metaDataID!, chunkIndex: chunkIndex, completion: {(result, chunkSize, error) in
   // I want to enter immediately when 'uploadFile' get called })

请求并没有真正等到所有请求都已发送。当一切正常时,每个回调都会在关联的请求完成时发生,并且更早发生这种情况是没有意义的,因为回调提供了来自服务器的响应(直到请求之后你才能返回)已全部发出)。

这里的问题是您通过同时启动太多任务完全阻塞了会话。 NSURLSession 中有一个已知错误,当​​您在单个会话中同时创建大量任务时,它会导致它开始崩溃。当您在会话中获得太多任务时,IIRC,会话将完全停止调用回调,基本上会话变得不可用。 (几年前讨论过另一个 Stack Overflow 问题,但我现在似乎找不到它。)

并且由于任务永远不会完成,您的应用程序最终会泄漏您用于正文数据的所有内存,这意味着您的应用程序只会分配越来越多的内存,直到它被逐出。

解决此问题的唯一方法是立即停止将所有请求添加到会话中。首先开始几个任务(最多八个部分左右),然后等待发送下一个部分,直到前面的部分完成或失败。这种方法不仅会阻止您阻塞 NSURLSession,还会阻止您分配大量内存来保存所有请求主体 NSData 对象,这些对象目前都同时位于 RAM 中。

我建议保留一个 NSNumber 对象的 NSMutableArray 来代表每个未发送的块。这样,您就知道还剩下什么要发送,您可以循环到 8 并提取前 8 个数字,然后发送包含这些数字的数据块。当请求成功完成时,从数组中获取下一个数字并发送包含该数字的块。

此外,您不应在特定次数的重试后停止。相反,当请求失败时,检查失败以决定是重试(网络故障)还是放弃(服务器错误)。然后用reachability等到合适的时候再试,说目的主机可达的时候再试。仅当用户通过点击取消按钮或类似按钮明确要求您取消上传时才取消上传。如果用户要求您取消上传,请拆除您的数据结构,这样您就不会启动任何新请求,然后使 URL 会话无效。