Vast memory required with UICollectionView images set from NSDictionary - ios

I'm making an app which has lots of entries from a database displayed in a UICollectionView, and each item in the collectionview displays an image. However, there are lots of duplicate images (ie, my UICollectionView has 200 items but there's only about 30 possible images).
Initially I assigned these images by calling cell.imageview.image =[UIImage imageWithContentsOfFile] in my cellForItemAtIndexPath method, but that caused a bit of scrolling lag, presumably due to having to read the image from the internal memory each time.
Now I want to do all the I/O in viewDidLoad by creating an array/dictionary and just referencing that each time cellForItem is called. When I do this and create a dictionary of UIImages in viewDidLoad and have cell.imageview.image = [imageDictionary objectForKey:] it displays images correctly and scrolls really smoothly but the memory used is huge (it just increases till it's scrolled all the way to the bottom so I presume it has all 200 image instances in the RAM).
However, if alternatively I fill the dictionary in viewDidLoad with NSData of the image and set the image using cell.imageView = [UIImage imageWithData:[dataDictionary objectForKey:]], the memory used is far more efficient and reflects only what is currently displayed on the screen. However, the scrolling becomes slightly laggy again.
My question is: why does the memory leak only occur when I fill the dictionary with UIImages rather than NSData? Does iOS automatically cache images unexpectedly or something?
Hope that made sense - it's hard to be clear without going too much into the detail of my code.

Use your first approach to set the image as you scroll. However, load the images in a background thread and then set the image on the main thread so you don't inhibit scrolling. Also, make sure your images are the same size as the image view (ie. not huge) so they don't take up a ton of memory.
Example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
UIImage *image = [UIImage imageWithContentsOfFile... // do stuff
dispatch_async(dispatch_get_main_queue(), ^(void) {
[[cell imageView] setImage:image];
});
});

Related

iOS - Should A Hidden UIImageView Of A UITableViewCell NOT Have An Assigned Image Until It's Visible?

I have UITableViewController where in its cellForRowAtIndexPath method, if a certain condition is true about the UITableViewCell, I make its UIImageView visible. In my storyboard, I already have the UIImageView with an assigned image and hidden by default.
My question is this. Is it better, in terms of limiting my app's memory usage, to have the UIImageView NOT assigned to an image in the storyboard and just call this code when it needs to be displayed? :
myCell.myImageView.image = [UIImage imageNamed:#"myImage.png"];
myCell.myImageView.hidden = NO;
I'm asking this because I'm wondering if the constant repetition of images will hog up too much memory even though they're hidden, and if it's better to ONLY assign an image when the UIImageView's hidden attribute is about to be set to NO (to make it visible).
imageNamed: caches the image and is dumped when your app reaches a memory warning.
If the same key (#"myImage.png") is used in multiple places it reads from the same memory space and does not create multiple instances of the bitmap.
Memory issues with images crop up mostly when you're holding onto lots of large images strongly. There's many strategies, like using SDWebImage and letting that handle your image cache.
As always, resize your images as best you can to the size of the device that's viewing it, implement sensible caches and don't worry about memory so much!
You can check if your memory is being eaten up with the memory chart under the "Debug Navigator" (command - 6) and just scroll to see how your memory is being used.

Memory not released after removing UIImageView from superview

On iOS 6.1, I have a view controller with a scrollView in which I need to display some images as the user flicks through the pages. The scrollView has 3 subviews to display the current, the previous and the next image respectively. Images are substituted during the scrolling.
The mechanism works fine, but resources are not released as expected and the app is terminated after scrolling approximately 15 images.
At first I tried solving the problem simply assigning the new image to the UIImageView, but it did not work (I read that images are cached), so I tried a different approach:
// access the UIImageView of the currentView
for (UIView *subviewOfCurrentView in [currentView subviews]) {
if ([subviewOfCurrentView isKindOfClass:[UIImageView class]]) {
UIImageView *cv = (UIImageView *)subviewOfCurrentView;
//cv.image = nil;
[cv removeFromSuperview];
UIImageView *new_cv = [[UIImageView alloc] initWithFrame:photoRect];
new_cv.image = [UIImage imageNamed:[myObject getFileName];
[currentView addSubview:new_cv];
}
}
Profiling the app with the Activity Monitor shows that the Real Memory Usage keeps growing when I flick an image, despite the fact that the number of subviews remains constant.
Note that the app is terminated without calling didReceiveMemoryWarning.
How can I force my app to release the memory used by UIImage(s) and UIImageView(s)?
If you can help me, much appreciated.
UIImage loaded by imageNamed: are are not automatically released (they are cached and images with the same name share the same data).
Use imageWithContentsOfFile: or imageWithData:scale: if you want the image to be automatically released. Note that you can get the image path using methods from NSBundle although it will be a bit more complicated.
You will have to implement the following part by yourself
If the screen has a scale of 2.0, this method first searches for an image file with the same filename with an #2x suffix appended to it. For example, if the file’s name is button, it first searches for button#2x. If it finds a 2x, it loads that image and sets the scale property of the returned UIImage object to 2.0. Otherwise, it loads the unmodified filename and sets the scale property to 1.0.
Not sure how to get path for an image when using the new iOS 7 image stores.

Load local UIImage asynchronously with caching

UIScrollViewController
|-----UIImageView1, 2, 3, 4, 5...
I have a UIScrollViewController which has several subviews. Every subview contains a UIImageview, when scrolling the UIImageView, pages on either side of current page will be loaded using
imageView = [[UIImageView alloc] initWithContentsOfFile:fileName];
But when the local image file becomes really big (>2MB), the scrolling becomes very influent amd gets stuck.
Is there a way to avoid this? For example using a cache or doing it asynchronously?
You can load images asynchronously, in another thread, or if you support only iOS higher than 4, it's quite easy to perform it with blocks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
UIImageView* imageView = [[[UIImageView alloc] initWithContentsOfFile:fileName]autorelease];
dispatch_async(dispatch_get_main_queue(), ^{
//Maybe correct the frame too
[self.view addSubview:imageView];
});
});
If you can't use blocks, you can execute in different thread, but it's a bit more involved.
Anyway, I recommend:
Better have created UIImageViews added to the scrollview, with a 'dummy' or 'spinner', then when user interaction stops (detected via delegate method), you can reload the visible imageviews.
When user is scrolling, better to load and cache UIImage objects for the images you want, (store them in an array or dictionary for example).
Scale the UIImage content to fit the UIImageView frame inside ScrollView. That way during rendering time, UIImage will not 'autoscale' the provided image, so it will be faster.

ScrollView too slow – Memory management issue?

In my app, I use a scroll view in which I display 300 image (400 kB).
It takes nearly 2 minutes, and as a result, when I scroll up and down, it isn't smooth.
How can I solve this problem?
Note: I have noticed that after I scroll down to end of scrollview once, it gets smooth.
The issue with scrolling only appears the first time.
Try adding this in your load function (where you load your images). If you are using a while or for loop to load images, add this code to the loop.
NSURL *url=//here url for your image
dispatch_queue_t currentQueue = dispatch_get_current_queue();
dispatch_queue_t dataConvertorQueue = dispatch_queue_create("Fetching images", NULL);
dispatch_async(dataConvertorQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(currentQueue, ^{
//replace imageView with your imageView outlet
imageView.image = [[UIImage alloc] initWithData:data]];
});
});
dispatch_release(dataConvertorQueue);
This code helps you fetch and show images from a url and it works great with a tableview. It might not be exactly what you want but it'll give you an idea on how to do it. Hope it'll help you.
Do not load all images upfront. Just load the images to show at the moment and the next + last picture.
As far as i understand you load all the images when the scrollview is loaded/displayed. Since you have a lot of images it will be much faster and much more memory saving if you just load/have in memory three images.
This will be the previous/left shown image and not visible - the current shown and visible image and the next/right image which is not visible.
So if you move/swipe to the left you basicly move the scrollview to the left and show the right image which is already loaded. In the background you will load the next right image while you will deallocate the left most image.
At all times you just have maximal 4 images loaded.
Take a look at the infinite scrollview example of WWDC 2011 - Session 104 advanced scroll view for a good visual example and sample code.
I think this will solve your problem. This question may give you some context:
How to reuse/recycle custom element like uitableviewcell does?

Why can't I update the contents of my UIScrollView using NSOperationQueue / NSOperationInvocation?

I think I remember something about UI being updated on the main thread, could be the explanation, however my issue is that I have a UIScrollView with 1000+ images in it, and I wrote a routine that quickly will toggle the images visible in the view to show their real image, and when out of visible view, they switch to a placeholder image. All the offscreen images share the same placeholder, greatly reducing memory load.
I wrote two methods that toggle the next few tiles of the view on/off, and are kicked off if you have scrolled enough in a certain direction. The scrolling is detected via the UIScrollViewDelegate callback method scrollViewDidScroll.
This strategy works fine, but the scrollview scrolling is a little jittery on slow devices (iPad 1). I wanted to increase performance, so I thought I could change the code so that the two methods that update the scrollview contents were NSOperationInvocation, added to an NSOperationQueue.
However, when I do this, even with priority set to VeryHigh, the routines to not update the UI even though they seem to be called, and the queue does grow, when scrolling. The observed behavior is that the images retain their placeholder images and do not switch on.
Why doesn't this work?
Code for the NSOperationInvocation:
ImageThumbView *thumb = [boardThumbsArray objectAtIndex:lowestLoadedThumbTag - i];
[loadedThumbsArray addObject:thumb];
[thumb showThumbImage];
ImageThumbView is a UIButton Subclass
- (void)showThumbImage {
if (thumbImagePath != nil) {
thumbImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL fileURLWithPath:thumbImagePath]]];
} else {
NSLog(#"%#", #"Cannot show thumb image: thumbImagePath is nil");
}
[self setBackgroundImage:thumbImage forState:UIControlStateNormal];
[self setNeedsDisplay];
}
Look at Apple's "PhotoScroller" sample code. You should not be having 1000 views as subviews of scrollview. Recycle the ones that are no longer visible.
Also look at WWDC 2010 "Designing apps with scrollview" video. That explains the photoscroller sample code
Thanks

Resources