So I hit another roadblock in my endeavors with Swift. I am trying to load multiple images into an image gallery - all works fine except of one thing. The memory use of the application keeps growing and growing despite the fact that I clear the images. After eleminating basically all the code, I found out that this is caused by my image loading script:
所以我在与斯威夫特的合作中遇到了另一个障碍。我正在尝试将多个图像加载到一个图像库中——除了一件事之外,所有的工作都很好。应用程序的内存使用一直在增长和增长,尽管我清除了映像。基本上所有的代码都解析后,我发现这是由于我的图像加载脚本造成的:
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()
}
As you can see this piece of code does basically nothing right now. Yet every time I call it, my apps memory usage grows. If I comment out the query, the memory usage is not changing.
正如您所看到的,这段代码现在基本上什么都不做。但每当我调用它时,我的应用程序内存使用量就会增长。如果我注释掉查询,内存使用不会改变。
I have read several similar issues but they all involved the use of a delegate. Well, in this case there is no delegate yet there is the memory issue. Does anybody know how to eliminate it and what is causing it?
我读过几个类似的问题,但都涉及到委托的使用。在这种情况下,还没有委托存在内存问题。有人知道如何消除它吗?是什么引起的?
EDIT: Here is a complete test class. Seems like the memory grows only when the image could be loaded, like the pointers to the image would be kept in the memory for ever. When the image was not found, nothing happens, memory usage stays low. Maybe some hint how to clean those pointers?
编辑:这是一个完整的测试类。似乎只有当可以加载图像时,内存才会增长,就像指向图像的指针将永远保存在内存中一样。当没有找到映像时,什么也没有发生,内存使用率仍然很低。也许有什么建议可以帮助你清理指针?
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()
}
}
I am sorry, I have no idea how to use the profiler just yet (being very noob in this whole iOS jazz), at least I will attach a screenshot of the profiler that is produced by the code above:
很抱歉,我还不知道如何使用profiler(在整个iOS jazz中都是noob),至少我会附上上面代码生成的profiler的屏幕截图:
4 个解决方案
#1
23
You have to call invalidateAndCancel
or finishTasksAndInvalidate
on the session, first of all... or else, boom, memory leak.
您必须在会话中调用invalidateAndCancel或finishTasksAndInvalidate,首先…或者是内存泄漏。
Apple's NSURLSession Class Reference states in Managing the Session section in a sidebox:
苹果的NSURLSession类引用声明在管理侧边栏中的Session部分:
IMPORTANT --- The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
重要的——会话对象在应用程序退出或显式地使会话失效之前,对委托保持很强的引用。如果您没有使会话无效,那么您的应用程序将在它退出之前泄漏内存。
So yeah.
是的。
You might also consider these two methods:
你也可以考虑这两种方法:
- flushWithCompletionHandler:
- flushWithCompletionHandler:
Flushes cookies and credentials to disk, clears transient caches, and ensures that future requests occur on a new TCP connection.
将cookie和凭据刷新到磁盘,清除临时缓存,并确保未来的请求发生在新的TCP连接上。
- resetWithCompletionHandler:
- resetWithCompletionHandler:
Empties all cookies, caches and credential stores, removes disk files, flushes in-progress downloads to disk, and ensures that future requests occur on a new socket.
清空所有cookie、缓存和凭据存储,删除磁盘文件,将正在进行的下载刷新到磁盘,并确保未来的请求发生在新的套接字上。
... as directly quoted from the aforementioned NSURLSession Class Reference.
…直接引用上述NSURLSession类引用。
I should also note that your session config can have an effect:
我还应该注意,您的会话配置可以产生以下效果:
- (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;
}
One key, if you are not using the [NSURLSession sharedSession]
singleton, is for you to have your own custom singleton NSObject subclass that has a session as a property. That way, your session object gets reused. Every session will create an SSL cache associated to your app, which takes 10 minutes to clear, if you allocate new memory for a new session with each request, then you will see unbounded memory growth from SSL caches that persist for 10 minutes regardless of whether or not you invalidateAndCancel
or flush/reset the session.
如果您没有使用[NSURLSession sharedSession] singleton,那么有一个关键字是,您有自己的自定义单例NSObject子类,它具有一个会话作为属性。这样,您的会话对象将被重用。每一个会话将创建一个SSL缓存相关的应用程序,它需要10分钟清晰,如果你分配新的内存与每个请求一个新的会话,那么您将看到无限从SSL缓存内存增长,持续10分钟无论你是否invalidateAndCancel或冲洗/重置会话。
That's because Security framework privately manages the SSL cache, but charges your app for the memory it locks up. This will happen whether or not you set your configuration's URLCache
property to nil.
这是因为安全框架私有地管理SSL缓存,但是为它锁定的内存向应用程序收费。无论您是否将配置的URLCache属性设置为nil,都会发生这种情况。
For example, if you are in the habit of doing say, 100 different web requests per second, and each one uses a new NSURLSession object, then you're creating something like 400k of SSL cache per second. (I have observed that each new session is responsible for appx. 4k of Security framework allocations.) After 10 minutes, that's 234 megabytes!
例如,如果您习惯每秒处理100个不同的web请求,并且每个请求都使用一个新的NSURLSession对象,那么您将每秒创建400k的SSL缓存。(我注意到每个新会话负责appx。4k的安全框架分配。10分钟后,就是234兆!
So take a cue from Apple and use a singleton with an NSURLSession property.
因此,请参考苹果的经验,使用带有NSURLSession属性的单例。
Note that the reason the backgroundSessionConfiguration
type saves you this SSL cache memory, and all other cache memory like NSURLCache, is because a backgroundSession delegates its handling to the system who now makes the session, not your app, so it can happen even if your app is not running. So it's just hidden from you... but it's there... so I would not put it past Apple to reject your app or terminate its background sessions if there is huge memory growth back there (even though Instruments won't show it to you, I bet they can see it).
请注意,backgroundSessionConfiguration类型为您节省了这个SSL高速缓存内存,以及所有其他缓存内存,比如NSURLCache,这是因为backgroundSession将它的处理委托给了系统,而该系统现在使会话,而不是您的应用程序,所以即使您的应用程序没有运行,它也会发生。所以它只是对你隐藏……但它的存在……所以我不会让苹果拒绝你的应用程序,或者在内存增长巨大的情况下终止它的后台进程(即使设备不会显示给你,我打赌他们可以看到)。
Apple's docs say that backgroundSessionConfiguration
has a nil default value for the URLCache, not just a zero capacity. So try an ephemeral session or default session and then set its URLCache property to nil, as in my example above.
苹果的文档称,backgroundSessionConfiguration对URLCache具有nil默认值,而不仅仅是零容量。因此,尝试一个临时会话或默认会话,然后将其URLCache属性设置为nil,就像我在上面的示例中那样。
It's probably also a good idea to set your NSURLRequest object's cachePolicy property to NSURLRequestReloadIgnoringLocalCacheData
also, if you are not going to have a cache :D
如果没有缓存,也可以将NSURLRequest对象的cachePolicy属性设置为NSURLRequestReloadIgnoringLocalCacheData
#2
8
I faced a similar issue in a recent app.
在最近的一款应用中,我也遇到过类似的问题。
In my situation I was downloading a number of images from an API. For each image I was creating an NSURLSessionDownloadTask and adding it to a NSURLSession with an ephemeral configuration. When the task was complete I called a completion block to process the downloaded data.
在我的情况下,我从一个API下载了许多图片。对于每个映像,我都在创建一个NSURLSessionDownloadTask,并将其添加到带有临时配置的NSURLSession中。当任务完成时,我调用一个完成块来处理下载的数据。
Each download task that I added caused additional memory to be allocated (according to the Xcode debugger) that was not released at any point thereafter. When downloading approximately 100 images, the debugger had the memory usage as ~600 MB. The more download tasks, the more memory, that was allocated and not released. At no point had I displayed or used the image data in any way, it was simply stored to disk.
我添加的每个下载任务都会分配额外的内存(根据Xcode调试器),此后任何时候都不会释放这些内存。当下载大约100个图像时,调试器的内存使用约为600 MB。我没有以任何方式显示或使用图像数据,只是将其存储到磁盘。
Trying to diagnose the issue in instruments was not fruitful. Instruments showed no leaks, and no allocations corresponding to the download tasks or image data. There were no memory leaks due to retain cycles in my blocks or the like, only in the debugger did the memory spiral.
试图用仪器来诊断这一问题是没有结果的。仪器显示没有泄漏,也没有相应的分配给下载任务或图像数据。由于在我的块或类似的块中保留了循环,所以没有内存泄漏,只有在调试器中才能实现内存螺旋。
After several hours of trial and error including:
经过数小时的反复试验,包括:
-
Downloading the images with the completion block set to nil (to ensure I did not have a retain cycle in the block).
下载图像时将完成块设置为nil(以确保在块中没有保留周期)。
-
Commenting out various parts of my code to make sure that the allocations occurred precisely when '[downloadTask resume]' was called.
注释掉代码的各个部分,以确保在调用“[downloadTask resume]”时准确地执行分配。
-
Setting the URLCache for the session to both nil and a cache with a size of 0. As per: http://www.drdobbs.com/architecture-and-design/memory-leaks-in-ios-7/240168600
将会话的URLCache设置为nil,将缓存设置为0。按:http://www.drdobbs.com/architecture-and-design/memory-leaks-in-ios-7/240168600
-
Changing the NSURLSession configuration to the default configuration.
将NSURLSession配置更改为默认配置。
Finally I switched my NSURLSession configuration from an ephemeral type to a background type. This required that I not use a completion block to process the images. I instead processed them in the delegate. This solved the issue for me. Adding download tasks to the background NSURLSession resulted in almost zero memory growth as the downloads were initiated and processed.
最后,我将NSURLSession配置从临时类型切换到后台类型。这要求我不使用补全块来处理图像。而是在委托中进行处理。这就解决了我的问题。在后台NSURLSession中添加下载任务会导致在启动和处理下载时内存增长几乎为零。
I wish I had a better understanding of why this is the case, but I unfortunately do not. I have seen this issue crop up for others as well and have yet to come across a better solution or explanation.
我希望我能更好地理解为什么会这样,但不幸的是我没有。我也看到过这个问题也会出现在其他人身上,而且还没有找到更好的解决方案或解释。
#3
0
After URLSession resumes its data task ,If you no longer need a session, you can invalidate it by calling either invalidateAndCancel() (to cancel outstanding tasks) or finishTasksAndInvalidate() (to allow outstanding tasks to finish before invalidating the object). i think you are not invalidating the URLSession.So here memory leaks happen so you need to add any one of those above function and check in instruments in memory leaks sections .and your modified function will be like this
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
-3
@user3847320 ANSWER should be the accepted one !!! Also Valide for AFNetWorking :
@user3847320的答案应该被接受!!也验证网络:
NSURLSessionConfiguration* configurationSession = [NSURLSessionConfiguration backgroundSessionConfiguration:@"backgroundSession"];
_currentSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configurationSession];
NSURLSessionDownloadTask * downloadTask = [_currentSessionManager downloadTaskWithRequest:...];
#1
23
You have to call invalidateAndCancel
or finishTasksAndInvalidate
on the session, first of all... or else, boom, memory leak.
您必须在会话中调用invalidateAndCancel或finishTasksAndInvalidate,首先…或者是内存泄漏。
Apple's NSURLSession Class Reference states in Managing the Session section in a sidebox:
苹果的NSURLSession类引用声明在管理侧边栏中的Session部分:
IMPORTANT --- The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
重要的——会话对象在应用程序退出或显式地使会话失效之前,对委托保持很强的引用。如果您没有使会话无效,那么您的应用程序将在它退出之前泄漏内存。
So yeah.
是的。
You might also consider these two methods:
你也可以考虑这两种方法:
- flushWithCompletionHandler:
- flushWithCompletionHandler:
Flushes cookies and credentials to disk, clears transient caches, and ensures that future requests occur on a new TCP connection.
将cookie和凭据刷新到磁盘,清除临时缓存,并确保未来的请求发生在新的TCP连接上。
- resetWithCompletionHandler:
- resetWithCompletionHandler:
Empties all cookies, caches and credential stores, removes disk files, flushes in-progress downloads to disk, and ensures that future requests occur on a new socket.
清空所有cookie、缓存和凭据存储,删除磁盘文件,将正在进行的下载刷新到磁盘,并确保未来的请求发生在新的套接字上。
... as directly quoted from the aforementioned NSURLSession Class Reference.
…直接引用上述NSURLSession类引用。
I should also note that your session config can have an effect:
我还应该注意,您的会话配置可以产生以下效果:
- (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;
}
One key, if you are not using the [NSURLSession sharedSession]
singleton, is for you to have your own custom singleton NSObject subclass that has a session as a property. That way, your session object gets reused. Every session will create an SSL cache associated to your app, which takes 10 minutes to clear, if you allocate new memory for a new session with each request, then you will see unbounded memory growth from SSL caches that persist for 10 minutes regardless of whether or not you invalidateAndCancel
or flush/reset the session.
如果您没有使用[NSURLSession sharedSession] singleton,那么有一个关键字是,您有自己的自定义单例NSObject子类,它具有一个会话作为属性。这样,您的会话对象将被重用。每一个会话将创建一个SSL缓存相关的应用程序,它需要10分钟清晰,如果你分配新的内存与每个请求一个新的会话,那么您将看到无限从SSL缓存内存增长,持续10分钟无论你是否invalidateAndCancel或冲洗/重置会话。
That's because Security framework privately manages the SSL cache, but charges your app for the memory it locks up. This will happen whether or not you set your configuration's URLCache
property to nil.
这是因为安全框架私有地管理SSL缓存,但是为它锁定的内存向应用程序收费。无论您是否将配置的URLCache属性设置为nil,都会发生这种情况。
For example, if you are in the habit of doing say, 100 different web requests per second, and each one uses a new NSURLSession object, then you're creating something like 400k of SSL cache per second. (I have observed that each new session is responsible for appx. 4k of Security framework allocations.) After 10 minutes, that's 234 megabytes!
例如,如果您习惯每秒处理100个不同的web请求,并且每个请求都使用一个新的NSURLSession对象,那么您将每秒创建400k的SSL缓存。(我注意到每个新会话负责appx。4k的安全框架分配。10分钟后,就是234兆!
So take a cue from Apple and use a singleton with an NSURLSession property.
因此,请参考苹果的经验,使用带有NSURLSession属性的单例。
Note that the reason the backgroundSessionConfiguration
type saves you this SSL cache memory, and all other cache memory like NSURLCache, is because a backgroundSession delegates its handling to the system who now makes the session, not your app, so it can happen even if your app is not running. So it's just hidden from you... but it's there... so I would not put it past Apple to reject your app or terminate its background sessions if there is huge memory growth back there (even though Instruments won't show it to you, I bet they can see it).
请注意,backgroundSessionConfiguration类型为您节省了这个SSL高速缓存内存,以及所有其他缓存内存,比如NSURLCache,这是因为backgroundSession将它的处理委托给了系统,而该系统现在使会话,而不是您的应用程序,所以即使您的应用程序没有运行,它也会发生。所以它只是对你隐藏……但它的存在……所以我不会让苹果拒绝你的应用程序,或者在内存增长巨大的情况下终止它的后台进程(即使设备不会显示给你,我打赌他们可以看到)。
Apple's docs say that backgroundSessionConfiguration
has a nil default value for the URLCache, not just a zero capacity. So try an ephemeral session or default session and then set its URLCache property to nil, as in my example above.
苹果的文档称,backgroundSessionConfiguration对URLCache具有nil默认值,而不仅仅是零容量。因此,尝试一个临时会话或默认会话,然后将其URLCache属性设置为nil,就像我在上面的示例中那样。
It's probably also a good idea to set your NSURLRequest object's cachePolicy property to NSURLRequestReloadIgnoringLocalCacheData
also, if you are not going to have a cache :D
如果没有缓存,也可以将NSURLRequest对象的cachePolicy属性设置为NSURLRequestReloadIgnoringLocalCacheData
#2
8
I faced a similar issue in a recent app.
在最近的一款应用中,我也遇到过类似的问题。
In my situation I was downloading a number of images from an API. For each image I was creating an NSURLSessionDownloadTask and adding it to a NSURLSession with an ephemeral configuration. When the task was complete I called a completion block to process the downloaded data.
在我的情况下,我从一个API下载了许多图片。对于每个映像,我都在创建一个NSURLSessionDownloadTask,并将其添加到带有临时配置的NSURLSession中。当任务完成时,我调用一个完成块来处理下载的数据。
Each download task that I added caused additional memory to be allocated (according to the Xcode debugger) that was not released at any point thereafter. When downloading approximately 100 images, the debugger had the memory usage as ~600 MB. The more download tasks, the more memory, that was allocated and not released. At no point had I displayed or used the image data in any way, it was simply stored to disk.
我添加的每个下载任务都会分配额外的内存(根据Xcode调试器),此后任何时候都不会释放这些内存。当下载大约100个图像时,调试器的内存使用约为600 MB。我没有以任何方式显示或使用图像数据,只是将其存储到磁盘。
Trying to diagnose the issue in instruments was not fruitful. Instruments showed no leaks, and no allocations corresponding to the download tasks or image data. There were no memory leaks due to retain cycles in my blocks or the like, only in the debugger did the memory spiral.
试图用仪器来诊断这一问题是没有结果的。仪器显示没有泄漏,也没有相应的分配给下载任务或图像数据。由于在我的块或类似的块中保留了循环,所以没有内存泄漏,只有在调试器中才能实现内存螺旋。
After several hours of trial and error including:
经过数小时的反复试验,包括:
-
Downloading the images with the completion block set to nil (to ensure I did not have a retain cycle in the block).
下载图像时将完成块设置为nil(以确保在块中没有保留周期)。
-
Commenting out various parts of my code to make sure that the allocations occurred precisely when '[downloadTask resume]' was called.
注释掉代码的各个部分,以确保在调用“[downloadTask resume]”时准确地执行分配。
-
Setting the URLCache for the session to both nil and a cache with a size of 0. As per: http://www.drdobbs.com/architecture-and-design/memory-leaks-in-ios-7/240168600
将会话的URLCache设置为nil,将缓存设置为0。按:http://www.drdobbs.com/architecture-and-design/memory-leaks-in-ios-7/240168600
-
Changing the NSURLSession configuration to the default configuration.
将NSURLSession配置更改为默认配置。
Finally I switched my NSURLSession configuration from an ephemeral type to a background type. This required that I not use a completion block to process the images. I instead processed them in the delegate. This solved the issue for me. Adding download tasks to the background NSURLSession resulted in almost zero memory growth as the downloads were initiated and processed.
最后,我将NSURLSession配置从临时类型切换到后台类型。这要求我不使用补全块来处理图像。而是在委托中进行处理。这就解决了我的问题。在后台NSURLSession中添加下载任务会导致在启动和处理下载时内存增长几乎为零。
I wish I had a better understanding of why this is the case, but I unfortunately do not. I have seen this issue crop up for others as well and have yet to come across a better solution or explanation.
我希望我能更好地理解为什么会这样,但不幸的是我没有。我也看到过这个问题也会出现在其他人身上,而且还没有找到更好的解决方案或解释。
#3
0
After URLSession resumes its data task ,If you no longer need a session, you can invalidate it by calling either invalidateAndCancel() (to cancel outstanding tasks) or finishTasksAndInvalidate() (to allow outstanding tasks to finish before invalidating the object). i think you are not invalidating the URLSession.So here memory leaks happen so you need to add any one of those above function and check in instruments in memory leaks sections .and your modified function will be like this
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
-3
@user3847320 ANSWER should be the accepted one !!! Also Valide for AFNetWorking :
@user3847320的答案应该被接受!!也验证网络:
NSURLSessionConfiguration* configurationSession = [NSURLSessionConfiguration backgroundSessionConfiguration:@"backgroundSession"];
_currentSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configurationSession];
NSURLSessionDownloadTask * downloadTask = [_currentSessionManager downloadTaskWithRequest:...];