I have an app that needs to take screenshots and save them as files. I'm using ARC, so not releasing variables manually, and it seems my code has some serious leaks.
Here is what I'm running:
- (BOOL) saveNow:(NSString *)filePath {
UIImage *image = [self.view getImage];
NSData *imageData = UIImagePNGRepresentation(image);
return [imageData writeToFile:filePath atomically:YES];
}
Where getImage
is a method of a category on UIView
:
- (UIImage *)getImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [[UIScreen mainScreen]scale]);
[[self layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
When running this on a non-retina iPad, the creation of a UIImage
object fills the memory with an additional 1 MB, NSData
adds a further 4 MB, and as I run this many times, this memory is not released! On a retina iPad each call to saveNow:
costs ~17 MB, which causes the device to run out of memory after a few runs.
A little extra info. I'm running this code in a loop that iterates a total of over 300 times (small changes are made to the view on each iteration and I need a screenshot of each for review purposes). If I reduce the amount of iterations so that the device does not run out of memory, I can see that the memory is released once the method that contains the loop returns. However, this is not ideal, and I would expect that taking the memory heavy code out into it's own function (saveNow:
) should have made an improvement, but it does not. How can I force these objects to be released as soon as they are not needed instead of waiting for the parent method to return? Hopefully without having to disable ARC on the entire project.
Edit: I tried using @autoreleasepool
like this:
@autoreleasepool {
[self saveNow:filePath];
}
The results are better but not perfect. It releases about 4 MB of memory when the block is complete, but another 1 MB is still stuck until the container method returns. So it's an 80% improvement (yey!) but I'm aiming for 100% :) I'll read up more about @autoreleasepool
as I have not used it before.
I'll make my comment on @autoreleaspool a legitimate answer to help you out.
Apple suggests to use @autoreleaspool where the memory is in concern. The following paragraph is taken from Core Data documentation, but I believe can be applied in this situation as well:
In common with many other situations, when you use Core Data to import a data file it is important to remember “normal rules” of Cocoa application development apply. If you import a data file that you have to parse in some way, it is likely you will create a large number of temporary objects. These can take up a lot of memory and lead to paging. Just as you would with a non-Core Data application, you can use local autorelease pool blocks to put a bound on how many additional objects reside in memory. For more about the interaction between Core Data and memory management, see “Reducing Memory Overhead.”
Basically, @autoreleasepool serves as a hint for a compiler to release all temporary objects once they are out of bounds. You're expecting for the memory to be released completely, which might not be the case with Apple frameworks. There might be some caching going in behind the curtains (This is just and idea). That is why that remaining 1MB may be ok. However, just to be safe, I would recommend to increase the iteration number and see what happens.
As you mentioned in your comment your loop is big and nested, so there might be something else going on. Try to get rid of all extra operations and see what happens.
Hope this helps, Cheers!