使用 NSURLSession.downloadTaskWithURL 时的内存泄漏
Posted
技术标签:
【中文标题】使用 NSURLSession.downloadTaskWithURL 时的内存泄漏【英文标题】:Memory leak when using NSURLSession.downloadTaskWithURL 【发布时间】:2015-03-29 04:31:07 【问题描述】:因此,我在使用 Swift 的过程中遇到了另一个障碍。我正在尝试将多个图像加载到图像库中 - 除了一件事之外,一切都很好。尽管我清除了图像,但应用程序的内存使用量仍在不断增长。基本消除所有代码后,发现这是我的图片加载脚本造成的:
func loadImageWithIndex(index: Int)
let imageURL = promotions[index].imageURL
let url = NSURL(string: imageURL)!
let urlSession = NSURLSession.sharedSession()
let query = urlSession.downloadTaskWithURL(url, completionHandler: location, response, error -> Void in
)
query.resume()
正如您所见,这段代码现在基本上什么都不做。然而,每次我调用它时,我的应用程序内存使用量都会增加。如果我注释掉查询,内存使用不会改变。
我已经阅读了几个类似的问题,但它们都涉及使用委托。好吧,在这种情况下,没有委托,但存在内存问题。有谁知道如何消除它以及是什么原因造成的?
编辑:这是一个完整的测试类。似乎只有在可以加载图像时内存才会增长,就像指向图像的指针将永远保存在内存中一样。当找不到图像时,什么也没有发生,内存使用率保持在低水平。也许一些提示如何清理这些指针?
import UIKit
class ViewController: UIViewController
override func viewDidLoad()
//memory usage: approx. 23MB with 1 load according to the debug navigator
//loadImage()
//memory usage approx 130MB with the cycle below according to the debug navigator
for i in 1...50
loadImage()
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
func loadImage()
let imageURL = "http://mama-beach.com/mama2/wp-content/uploads/2013/07/photodune-4088822-beauty-beach-and-limestone-rocks-l.jpg" //random image from the internet
let url = NSURL(string: imageURL)!
let urlSession = NSURLSession.sharedSession()
let query = urlSession.downloadTaskWithURL(url, completionHandler: location, response, error -> Void in
//there is nothing in here
)
query.resume()
对不起,我还不知道如何使用分析器(在整个 ios 爵士乐中非常菜鸟),至少我会附上上面代码生成的分析器的屏幕截图:
【问题讨论】:
您在completionHandler
中使用什么代码?听起来像是一个保留周期正在发生。你可以使用工具来分析内存分配和磁盘的读/写吗?
@LouisTur 问题已更新。
将您的代码嵌入到 auroreleasepool 块中。我没有看到仪器报告的任何泄漏。
您在 1.2MB+ 的图像文件上调用此 loadImage
方法 50 次,使用下载任务将结果保存到磁盘。有可能这就是你记忆力猛增的原因。如果只调用一次,它的行为是否相同?
@LouisTur 我在这里感到困惑吗?内存信息是可用的 RAM 内存,不是吗?这里的问题不是尖峰,而是它实际上从未下降。我假设如果不访问文件数据,它就会被垃圾收集。但似乎并非如此。如果我调用一次这个方法,当然它只是在内存中产生了一点点差异,没有什么特别的。
【参考方案1】:
您必须在会话中调用invalidateAndCancel
或finishTasksAndInvalidate
,首先...否则,繁荣,内存泄漏。
Apple 的 NSURLSession 类参考在边框中的管理会话部分中声明:
重要 --- 会话对象保持对委托的强引用,直到您的应用退出或显式使会话无效。如果你不 使会话无效,您的应用会泄漏内存,直到退出。
是的。
你也可以考虑这两种方法:
flushWithCompletionHandler:resetWithCompletionHandler:将 cookie 和凭据刷新到磁盘,清除临时缓存,以及 确保将来的请求发生在新的 TCP 连接上。
清空所有 cookie、缓存和凭据存储,删除磁盘文件, 将正在进行的下载刷新到磁盘,并确保将来 请求发生在新的套接字上。
...直接引用自上述NSURLSession 类参考。
我还应该注意,您的会话配置可能会产生影响:
- (void)setupSession
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.URLCache = nil;
config.timeoutIntervalForRequest = 20.0;
config.URLCredentialStorage = nil;
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
self.defaultSession = [NSURLSession sessionWithConfiguration:config];
config = nil;
如果您不使用[NSURLSession sharedSession]
单例,关键是您拥有自己的自定义单例 NSObject 子类,该子类具有会话作为属性。这样,您的会话对象就会被重用。每个会话都会创建an SSL cache associated to your app, which takes 10 minutes to clear,如果您为每个请求的新会话分配新内存,那么无论您是否invalidateAndCancel
或刷新/重置会议。
这是因为安全框架私下管理 SSL 缓存,但会向您的应用收取其锁定的内存费用。无论您是否将配置的 URLCache
属性设置为 nil,都会发生这种情况。
例如,如果您习惯于每秒执行 100 个不同的 Web 请求,并且每个请求都使用一个新的 NSURLSession 对象,那么您正在创建类似 400k 的 SSL 缓存 每秒时间>。 (我观察到每个新会话负责大约 4k 的安全框架分配。) 10 分钟后,这是 234 兆字节!
所以从 Apple 那里得到启发,使用带有 NSURLSession 属性的单例。
请注意,backgroundSessionConfiguration
类型为您保存此 SSL 缓存内存以及所有其他缓存内存(如 NSURLCache)的原因是因为 backgroundSession 将其处理委托给现在创建会话的系统,而不是您的应用程序,因此它可以即使您的应用程序没有运行,也会发生。所以它只是对你隐藏......但它就在那里......所以如果那里有巨大的内存增长,我不会让 Apple 拒绝你的应用程序或终止它的后台会话(即使 Instruments 不会显示它)你,我敢打赌他们能看到)。
Apple 的文档说backgroundSessionConfiguration
的 URLCache 默认值为 nil,而不仅仅是零容量。因此,请尝试临时会话或默认会话,然后将其 URLCache 属性设置为 nil,如我上面的示例所示。
如果您不打算拥有缓存,将 NSURLRequest 对象的 cachePolicy 属性设置为 NSURLRequestReloadIgnoringLocalCacheData
也是一个好主意:D
【讨论】:
如果我们使用单例 URLSession,那么我们无法获得每个请求的委托,对吧?单例 URLSession 将有一个委托。在我的使用中,每个请求(包含在 NSOperation 中)创建一个 URLSession,然后实现它的委托来处理传入的数据、接收的数据等。你将如何使用单例 URLSession 处理这种情况?【参考方案2】:我在最近的一个应用中遇到了类似的问题。
在我的情况下,我从 API 下载了许多图像。对于每个图像,我都创建了一个 NSURLSessionDownloadTask 并将其添加到具有临时配置的 NSURLSession 中。任务完成后,我调用了一个完成块来处理下载的数据。
我添加的每个下载任务都会导致分配额外的内存(根据 Xcode 调试器),此后任何时候都没有释放。下载大约 100 个图像时,调试器的内存使用量约为 600 MB。下载任务越多,分配但未释放的内存就越多。我从来没有以任何方式显示或使用过图像数据,它只是简单地存储在磁盘上。
试图诊断仪器中的问题并没有成功。仪器显示没有泄漏,也没有与下载任务或图像数据相对应的分配。没有由于我的块等中的保留周期而导致内存泄漏,只有在调试器中才会出现内存螺旋。
经过几个小时的反复试验,包括:
下载完成块设置为 nil 的图像(以确保块中没有保留周期)。
注释掉我的代码的各个部分,以确保在调用“[downloadTask resume]”时准确进行分配。
将会话的 URLCache 设置为 nil 和大小为 0 的缓存。根据:http://www.drdobbs.com/architecture-and-design/memory-leaks-in-ios-7/240168600
将 NSURLSession 配置更改为默认配置。
最后,我将 NSURLSession 配置从临时类型切换为后台类型。这要求我不使用完成块来处理图像。我改为在委托中处理它们。这为我解决了这个问题。将下载任务添加到后台 NSURLSession 导致在启动和处理下载时内存增长几乎为零。
我希望我能更好地理解为什么会这样,但不幸的是我没有。我也看到其他人也遇到过这个问题,但还没有找到更好的解决方案或解释。
【讨论】:
我的内存使用率也很高,我可以确认从 defaultSessionConfiguration 切换到 backgroundSessionConfigurationWithIdentifier 最终解决了这个问题。 我遇到了同样的问题,这个答案帮助了我。后台配置 + 委托 = 没有内存泄漏。谢谢【参考方案3】:在 URLSession 恢复它的数据任务之后,如果你不再需要一个会话,你可以通过调用任何一个来使它失效 invalidateAndCancel()(取消未完成的任务) 或 finishTasksAndInvalidate() (允许未完成的任务在使对象无效之前完成)。 我认为你没有使 URLSession 失效。所以这里发生了内存泄漏,所以你需要添加上述任何一个函数并在内存泄漏部分检查工具。你修改的函数将是这样的
func loadImageWithIndex(index: Int)
let imageURL = promotions[index].imageURL
let url = NSURL(string: imageURL)!
let urlSession = NSURLSession.sharedSession()
let query = urlSession.downloadTaskWithURL(url, completionHandler: location, response, error -> Void in
)
query.resume()
query.finishTasksAndInvalidate()
【讨论】:
【参考方案4】:在我的项目中,这是因为默认的urlCache
可能会消耗一些内存。如果您想减少内存使用,请尝试将 urlCache 设置为 0
configuration.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
我的整个 urlSession 看起来像:
private var urlSession: URLSession =
let configuration = URLSessionConfiguration.default
configuration.allowsCellularAccess = true
configuration.httpShouldSetCookies = true
configuration.httpShouldUsePipelining = true
configuration.requestCachePolicy = .useProtocolCachePolicy
configuration.timeoutIntervalForRequest = 60.0
configuration.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
return URLSession(configuration: configuration)
()
【讨论】:
以上是关于使用 NSURLSession.downloadTaskWithURL 时的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?
Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)