首页 > 解决方案 > 使用完成处理程序进行异步调用的多个 URLSession dataTask 导致内存上升

问题描述

我正在快速开发一个上传项目。我正在使用 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()

---我从 tableview 控制器中的这个函数调用这个 URLSession 任务

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

标签: swiftnsurlsessioncompletionhandlernsurlsessiondatatask

解决方案


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

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

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

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

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

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


推荐阅读