IOS App keeps Images in Memory - ios

I have an app that has two view controllers with images
when I launch the app the memory increases and when I then segue to the other view controller and load the 2nd image ( I have a button to do this ) the memory goes up again.. this is what I would have expected..
However, when I dismiss the 2nd VC controller or removing the image in the 2nd VC via a button, and then dismiss the controller , the memory never goes down.
why is this, with an app with lots of images it could get large.. how do I release the memory of the 2nd VC or at least the memory of the 2nd image.
Im looking at memory under debug navigator in Xcode 8, when running the app.
xcode project can be found here , very simple

iOS will cache images. The documentation for UIImage(named:) says:
Discussion
This method looks in the system caches for an image object with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method locates and loads the image data from disk or from an available asset catalog, and then returns the resulting object.
The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used...
Special Considerations
If you have an image file that will only be displayed once and wish to ensure that it does not get added to the system’s cache, you should instead create your image using
imageWithContentsOfFile:. This will keep your single-use image out of the system image cache, potentially improving the memory use characteristics of your app.
Note, you don't control when this cache is freed. The OS does that as it sees fit (and generally in response to memory pressure).
Bottom line, we often don't worry about inherent caching by UIImage and simply ensure the app doesn't have its own memory issues, e.g. make sure deinit is getting called and/or use the "Debug Memory Graph" feature in Xcode to watch memory graph. Or if you want, programmatically set the image using UIImage(contentsOfFile:).

Related

How to debug heavy memory issues on a simple app

I’m facing memory issues in my app and I didn’t find a way to find out yet which objects/classes are using that memory.
The app is simple, a view controller with a gallery view of images (grid view just like Instagram Explore; collection view with xib cells) and when you tap one, it takes you to the next screen, which is the same set of images, but as a vertical list (uitableview with xib cells). The images are downloaded asynchronously from web.
The memory used by the app is continuously increasing when I scroll in both screens and also faster every time I open the list screen. Then the only moment when the memory used is reduced (and I mean drastically, like from 1.8GB to 200MB) is when it hits the device’s limit and then the issue appears again and again. Also, sometimes, the system fails to reduce the memory used and the app crashes ("Terminated iOS app due to memory issue").
I don’t think it’s a layout problem, I’ve checked all of that, also used memory graph debugger and only “malloc” issues found there which takes me nowhere, no class, no line, no nothing. Also, Instruments tool is too complex and I don’t know how to deal with it yet.
I’ve read some tutorials and tried some solutions but nothing worked. There are included: https://krakendev.io/blog/weak-and-unowned-references-in-swift, http://iosbrain.com/blog/2018/07/22/finding-memory-leaks-with-the-xcode-memory-graph-debugger-and-fixing-leaks-with-unowned/, https://www.youtube.com/watch?v=1LnipXiSrSM&t=1697s, https://developer.apple.com/videos/play/wwdc2018/416/
Can somebody give me some other advice or tutorials on how to properly debug memory issues to be able to find out their exact origin?
Most contemporary memory debugging strategies are geared for identifying and resolving strong reference cycles. But that’s not your issue here. The fact that most of the memory is recovered when you face memory pressure points to caching issues. Whatever further diagnostics you do is only likely to confirm this behavior.
To address this, set reasonable limits to your caches and avoid caches that don’t give you that control (e.g. UIImage(named:)) and the problem will likely be resolved. We cannot comment further without seeing how images are being retrieved (e.g. make sure the cache for the URLSession is reasonable) and how they’re being cached once downloaded (e.g. third party async image retrieval libraries generally give you control over the cache).
And, assuming you (or your third-party libraries) are caching, make sure that:
Test your app on the simulator, manually choosing “Debug” » “Simulate Memory Warning”. This will help confirm whether the app is responding to memory pressure. On the basis of what you describe, I think we already know this is the case, but it’s a good diagnostic.
Note, while we always want to make sure our apps respond to memory warnings correctly, when you face memory warnings, it may already be too late (e.g. the app may be doing a series of allocations and may fail before your app has a chance to react to the warning). You want to do whatever you can to manage caches before memory warnings occur.
You’re caching the original payloads (the Data object that contains the compressed jpg/png asset) and not UIImage objects (which, once they’re used, are uncompressed and can be huge if you’re not careful), or
If you do cache the UIImage objects, make sure to resize them as appropriate for your UI.
For example 100x100 image at 3x scale will take up 120kb, but if the image is 1000x1000px, even if the image view is only 100x100pt, the uncompressed image will take 4mb, i.e. 4 bytes per pixel, regardless of size of compressed jpg/png payload.
If you are using NSCache, set countLimit or totalCostLimit.
If you are doing your own collection (array or dictionary) of downloaded images, make sure you respond to memory pressure. E.g., in Swift:
NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: .main) { [weak self] _ in
// do whatever you need to remove your cached objects here
}
I don’t think that’s the issue here (because your app is responding to memory pressure), but you might want to review your app for anything else that (a) is large; (b) you are downloading and holding in memory yourself, and respond accordingly.
FWIW, I think you’ve done sufficient diagnostics to identify the source of the problem (the fact that it’s getting purged under memory pressure really does point to caching issues), but if you want to learn the “Allocations” tool in Instruments, check out these old WWDC videos Fixing memory issues and iOS App Performance: Memory. They’re old and focus on Objective-C, but the techniques outlined there are still applicable if you want to get up to speed on Instruments. But, like I said, I think you’ve already identified the issue.

Displaying multiple images causes massive memory usage

I have an app that allows a user to go into their gallery and select photos from their photo library to add to the app, it is displayed in a tableview
This is then added to an array which after UIImagePickerController fetches the image, reloads the collection view with the new images.
My problem is that this uses large amounts of memory. I need a way to display the photos added exactly like the native photos app on the iPhone.
I've looked in lazy loading but I have no idea where to begin.
Could someone please tell how I would go about displaying images in a UICollectionView with lazy loading, or at least reducing the amount of memory used.
The app is generally at normal use using around 10mb of memory. This increases to 50mb+ when displaying a multitude of images in the collectionView.
Thanks.
Holding high-resolution images into an array will generally be problematic. Also, using the full resolution images for thumbnail sized image views in collection view is an extravagant use of memory.
So, when the user selects an image, capture a reference to that asset's URL. Then, as images are required by cellForItemAtIndexPath, retrieve the image, resize it to thumbnail dimensions (e.g. you could use something like this) and use that in your cell's image view.
If you want to be elegant about it, implement a NSCache in which you'll cache previously resized images, but make sure you have reasonable retention rules and have that purge itself upon memory pressure. That way, cellForItemAtIndexPath can see if the image exists in its cache, and if so, use that, otherwise go back to the assets library and resize that image. But by using cache, you can speed up the process of scrolling back through images that were previously resized.
But the key is to avoid holding high resolution images in memory. And if you're going to hold even the thumbnails in memory, you might want to capture that in something like a NSCache rather than an array.

Best image caching strategy in iOS

In my app, I have a UITableView which displays fairly large images and loads a moderately designed Xib file to display it in. Each image is around 700KB to 1MB in size. The flow is virtually never ending, it loads more and more as we scroll down. So you can imagine that I am running into memory issues.
I have tried using SDImageCache and NSCache. The former used disk memory for caching images. In both cases, the caches somehow didn't clear images automatically. I had to manually clear them when I got a Received memory warning prompt. And each time I clear these caches, the memory freed seems to be lesser each subsequent time.
Now I confused as to which cache strategy I must use for such a long list of images. Might I be having some leaks somewhere? They certainly didn't show up when I profiled the app.
P.S.: I am loading the images from the web. Just to be clear.
From the docs:
UIImage
+(UIImage *)imageNamed:(NSString *)name
Discussion This method looks in the system caches for an image object
with the specified name and returns that object if it exists. If a
matching image object is not already in the cache, this method loads
the image data from the specified file, caches it, and then returns
the resulting object.
So I guess leaving this to the UIImage class is a good approach.
Hope this helps!
As we implemented it in both Android and iOS: once you can show on the screen only 2-3 images.
Load in memory 2 more for the downwards scroll and 2 more for the upwards one. So you have in memory 7 images. Display them. The other images must be stored in files (when you download them). If the user scrolls too fast, do not show all the sequence of the images, instead show some "loading" icons in place of the images. When the scrolling stops, show the appropriate image + the previous one + the next one + prepare 2 more (for upwards scroll) and 2 more for downwards scroll.

Real Memory Constantly Increasing - Removing Subviews from View - iOS (ARC)

I have an iPad app which is crashing on iPad (First model) as it is running out of memory.
In the app I have a main view which adds as subviews about 20 UIScrollViews (custom class) each containing a UIImageView and UIImage. When the user moves to the next page, I remove all these subviews from the superview and then add 20 new UIScrollViews to the same view.
If I profile the app for allocations and memory leaks everything is ok - the memory allocated stays at about 2MB while the user scrolls left and right.
However if I look at the real memory usage in the Activity Monitor I can see that every time the user moves to a new page, the real memory increases by about 20MB. Eventually after a few new pages the app size hits 150+ MB and crashes.
Can anyone suggest what might cause this type of behaviour and how I can further troubleshoot this ?
Just a bit more info on the app structure :
In view did load the images are loaded into an NSMutableArray using initWithContentsOfFile.
You should not be maintaining these images in an array. Images consume a disproportionate amount of your limited RAM. There are a couple of approaches:
If you want to keep it simple, just don't store the images anywhere. Load the image property of the UIImageView by loading the image via initWithContentsOfFile and call it a day.
If you want some RAM caching for performance reasons, you could use imageNamed instead of initWithContentsOfFile. When the app receives a memory warning, the cache will automatically be purged.
I'd be inclined to use initWithContentsOfFile, but then manually cache in my own NSCache (which is like a NSDictionary, except you can set a countLimit of how many images it should hang on to).
By the way, you don't describe what technically happens when "the user moves to the next page." If you're simply refreshing the existing controls on the existing view controller, then everything is probably fine (once you fix the NSMutableArray problem I discuss above). If you're pushing/presenting to another view controller or scrolling controls off screen but neglecting to remove the old ones from their superview, then that will also cause problems. You might want to clarify what you're doing there.
Bottom line, you just need to make sure that when you go from one page to another, that you're not maintaining strong references to any old images or controls.

How to reorganize iOS project to avoid memory issues

I am building my first project that is an interactive ebook app for the iPad
I started with the Single View App template from XCode
So far, the project is mostly a series of block animated transitions between UIImageViews and MPMovieController videos, very serial so far
Everything is coded within a single view under a single view - the image views fade in and out with alpha animations
I am beginning to run into memory issues. I've used memory instruments and see that most everything is loaded into memory at the beginning (images from the InterfaceBuilder) aside from some videos instantiated at runtime
My question is - how should I reorganize my code to better utilize memory? Should I separate into different views under one view controller, or have multiple view controllers?
And which might be the most straight forward to implement?
Images are very memory-intensive. So:
Do not load an image until you actually need it (for display). When you are done with it (the image view is no longer visible), release it (by setting that image view's image to nil). Do not maintain the images in an array or anything like that. Do not create the image views preloaded with images in advance in the nib.
When you actually need an image for display, load it in code using imageWithContentsOfFile:, not imageNamed:. Thus you prevent caching of the image.
It is a waste of memory to work with an image larger than the display size. If these images are large, you can save a lot of memory up front if you load the image at the actual size needed for display. This is easy to do with Image IO framework and CGImageSourceCreateThumbnailAtIndex.
I think you need to use multiple view controllers and separate your code in small separated views and objescts that controll you app flow..
On the one hand it's better to have multiple views and so on..but it's a pain to rewrite code you worked on (unless it's absolutely necessary).
In my opinion if you don't have anything unnecessary in memory (i.e. when you take a view off screen you release the memory it used) you don't need to do anything.
After all, even if you split the code as it should have been, if your memory management is good, it will take the exact same memory.
You should be able to do this with a single view controller:
Load your images lazily from code. Keep an array of the image names rather than the images themselves, and load them just before you need them.
Make sure your images aren't larger than they need to be.
Recycle your image views. If the user won't see more than two image views at a time (including both the from and to in a transition), you should only have two image views.
Don't worry about whether the images are cached; iOS's caches are designed to release their contents under stress. That said, do not implement your own caching system. You might not release images properly under stress. If you need caching, use NSCache.

Resources