Memory not released after removing UIImageView from superview - ios

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.

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.

Best practice for preloading and caching images (AFNetworking)

I was wondering what is the best practice for this very common action:
Say i have a collection view, and each cell of this collection view has an imageView that receives a url for an image.
The images are large, so i want to preload all the images in advance (or say 5 in advance).
I'm using AFNetworking imageView Category.
So here's what i did - creating a temp UIImageView and set the url to it each iteration, but it seems not to be working right. (the images still takes time to show up, while i want it to be instantly, and if i close the internet connection, the images do not load from cache).
- (void)preloadGalleryImages {
for (GalleryItem *item in _galleryItems) {
UIImageView *tempImageView = [[UIImageView alloc] init];
[tempImageView setImageWithURL:item.imageURL placeholderImage:IMAGE(#"placegolder")];
}
}
}
Thanks,
There is a universal way of preheating images in collection views that was implemented in one of the Apple PhotosKit samples. You can anticipate user actions and fetch images that might soon appear on the display (that are close to the viewport). This feature is implemented in Nuke and DFImageManager.

Vast memory required with UICollectionView images set from NSDictionary

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];
});
});

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.

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