UIImage memory not deallocating VM: ImageIO_JPEG_DATA? - ios

I have multiple collection views on screen at one time that scroll horizontally. They are all filled with images. All of these images are being loaded in the background through the Parse api. I am running Instrument's allocations and the anonymous VM: ImageIO_JPEG_DATA category is taking up a majority of the memory being used. All memory in the app equals to about 40 and then this category is over 55, which puts the total right around 100. That category never goes down at all and just stays consistent. What can I do to free up this memory from the images in my collection views?
Here is the code for my collection view:
.m for my collection view controller
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionViewCell" forIndexPath:indexPath];
if (cell) {
[cell.loadingImageIndicator setHidden:NO];
[cell.loadingImageIndicator startAnimating];
id photo = [self.collectionViewPhotos objectAtIndex:indexPath.item];
if ([photo isKindOfClass:[PFObject class]]) {
PFObject *photoObject = (PFObject *)photo;
PFFile *imageFile = [photoObject objectForKey:kPhotoPictureKey];
[cell.cellImage setFile:imageFile];
[cell.cellImage loadInBackground:^(UIImage *image, NSError *error) {
[cell.cellImage setContentMode:UIViewContentModeScaleAspectFill];
[cell.loadingImageIndicator stopAnimating];
[cell.loadingImageIndicator setHidden:YES];
}];
} else if ([photo isKindOfClass:[UIImage class]]) {
[cell.cellImage setImage:photo];
[cell.cellImage setContentMode:UIViewContentModeScaleAspectFill];
}
}
return cell;
}
.m for CollectionViewCell
- (void)prepareForReuse
{
self.cellImage.image = nil;
}
- (void)dealloc
{
self.cellImage = nil;
}
Edit: Photo of instruments

I had the same issue in a photo gallery-type app, and ran into the same problem with allocations in the so-called ImageIO_JPEG_DATA category accumulating and remaining "live" forever, causing my app to run out of memory. Oddly, this happenned only on the iPad I was testing on and NOT on the ios simulator (which showed no memory problems).
Brian's suggestion (below) worked for me. My app was originally using an array, each element of which contained - amongst other things - a UIImage. The images were used in various UIScrollViewControllers.
When I needed to load an image, if I used
[UIImage imageWithContentsOfFile:path]
rather than a direct reference to the UIImage in my array, the memory problem (caused by some inexplicable caching that ImageIO_Malloc was doing) was gone and the ImageIO_JPEG_DATA allocations stopped piling up and got released.
I would never have come up with this solution in a gazillion years on my own, so thanks to Brian.

Related

IOS Objc how to properly load UICollectionViewCell after Scrolling

i'm pretty new in obj/Xcode, and i need to do a UIcollectionView how take images from url request.
I've try to catch the scroll event with scrollViewDidScroll, then call the my request function with the parameter page + 1 or -1; depending of the scroll.
But everytime i'm going on infinite loop, and my cell images donc stop loading, i think its because my way is wrong :
Catching the scroll action :
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
{
[self reload];
}
The function reload send the request and set the imgs.
How can i load properly the pages, and not 1000times for every scrolling.
Sorry for my english, and thank you
You dont need to call reload like that. As you scroll, 'cellForItemAtIndexPath' is automatically called. If theres a previously used cell in the table (out of view) then the cell is re-used for the new position it is populating - hence 'dequeueReusableCellWithReuseIdentifier' (hope you're using that?!).
So what you need to do is have code on that method itself, that will automatically 'lazy load' the image you want. Heres a copy of a cellForItemOfIndexPath method that achieves just that - the image is populated when its downloaded.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
VideoSearchCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
YoutubeSearchResult *cellSearchResult = [self.ytSearchResults objectAtIndex:indexPath.row];
if (cellSearchResult.thumbnailImage.image == nil)
{
//NSLog(#"loading image");
__weak VideoSearchCell *weakCell = cell;
__weak YoutubeSearchResult *weakResult = cellSearchResult;
NSURL *url = [NSURL URLWithString:cellSearchResult.thumbUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
UIImage *placeholderImage = [UIImage imageNamed:#"your_placeholder"];
[cellSearchResult.thumbnailImage setImageWithURLRequest:request placeholderImage:placeholderImage success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
weakResult.thumbnailImage.image = image;
weakCell.imageView.image = image;
[weakCell setNeedsLayout];
} failure:nil];
} else {
if (cell.imageView.image != cellSearchResult.thumbnailImage.image)
{
cell.imageView.image = cellSearchResult.thumbnailImage.image;
}
}
return cell;
}
So to explain - VideoSearchCell is my custom cell type. Its fairly simple, it just has a UIImageView on it called 'imageView'. Ive also got an array called 'ytSearchResults' - this contains an array of objects and some contain images, and some dont. As the images download, the results also get populated to that array so I dont download them more than necessary.
The code checks to see if theres an image in that array. If not, it does the downloading code.
By the way, I use __weak references to properties, because of ARC and not wanting to accidentally retain the cell, or the search result. Not enough time to explain all that here though! Hope the code helps.
One more thing - setImageWithURLRequest is a method from the very handy and extremely popular library AFNetworking (2.0). Get hold of that as you will use it all the time.
I do not recommend to load images before cell is actually used, because you never know if user will see that cell. You can start image loading when
- collectionView:cellForItemAtIndexPath: is called, and before starting download request you must check if download of this image is in progress, otherwise you will load it more than once.

received memory warning then crash

I want to display multiple images in scrollview when images are more than 70 application will crash and display received memory error.I have get the images from document Directory
I have tried -
UIScrollView *MyScroll=[[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
int x=5,y=15;
for (int i = 0; i < imgarr.count; i++)
{
if(x<=211)
{
UIImage* imagePath = [UIImage imageWithContentsOfFile:[path_array objectAtIndex:i]];
imgView=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 77, 75)];
imgView.image=imagePath;
imgeBtn=[UIButton buttonWithType:UIButtonTypeCustom];
imgeBtn.frame=CGRectMake(x, y, 93, 110);
[imgeBtn addTarget:self action:#selector(btnclick:) forControlEvents:UIControlEventTouchUpInside];
imgeBtn.tag=i;
x+=103;
}
else
{
x=5;
y+=130;
UIImage* imagePath = [UIImage imageWithContentsOfFile:[path_array objectAtIndex:i]];
imgView=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 77, 75)];
imgView.image=imagePath;
imgeBtn=[UIButton buttonWithType:UIButtonTypeCustom];
imgeBtn.frame=CGRectMake(x, y, 93, 110);
[imgeBtn addTarget:self action:#selector(btnclick:) forControlEvents:UIControlEventTouchUpInside];
imgeBtn.tag=i;
x+=103;
}
[MyScroll addSubview:imgeBtn];
[MyScroll addSubview:imgView];
MyScroll.contentSize=CGSizeMake(320, y+120);
How can I display multiple images in scrollview?
The advice others have given to switch to a UICollectionView is good, but it has missed the real flaw in your code: the method imageNamed: of UIImage is keeping all 70 of your images in memory forever. Per the docs:
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.
Switching to this method will prevent the crashes, if you only load into memory the images that you need. UICollectionView makes this trivial, since you can only load image when the collection view asks you to fill out a cell object in its datasource's collectionView:cellForItemAtIndexpath: method.
Create custom class of collection view cell
and You can pass image name in array.
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UICollectionViewDataSource,UICollectionViewDelegate>
#property (weak, nonatomic) IBOutlet UICollectionView *Collection;
#end
#import "ViewController.h"
#import "CustomCell.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.Collection registerClass:[CustomCell class] forCellWithReuseIdentifier:#"cell"];
[self.Collection registerNib:[UINib nibWithNibName:#"CustomCell" bundle:nil] forCellWithReuseIdentifier:#"cell"];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 5;
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 4;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell * cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.imgview.image=[UIImage imageNamed:#"XYZ.png"];
return cell;
}
You are allocating and keep adding the images which causing the memory crash as all the images taking the device memory, There are many solution which build for showing the images and best is using the UICollectionView which load only the images which is showing the screen. You go for any good tutorial which can show you how to do it, here is the reference
Maybe NSCache can help you.
1
When you are displaying image at index 10.You can cache image 5-9 and 11-16 to NSCache/
2
When scrollView start Scrolling, get image from NSCache you need,and remove the object you doesn't need to display.
3
I think this can be efficient. Apple provide a demo how they handle the massive assets using caches and UICollectionView.You take a look at it.
https://developer.apple.com/library/ios/samplecode/UsingPhotosFramework/Introduction/Intro.html
I have also faced this same issue which i have resolved using below simple line.
instead of using this:
[UIImage imagedNamed:#"yourImage"];
try using this:
[yourCell.imageview setImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:#"yourImage" ofType:#"png"]]];
i hope it would solve your problem as by using imageWithContentsOfFile releases memory. so you won't get Memory Warning
UPDATE
You may also display images from any Remote URL and also from your application's document directory using other methods of UIImage Class
do tell if it helps
You may try with UITableView . Suppose you have n number of Images then -
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// 3 is numbers of images per row
if (n/3 *3 == n)
return n/3;
return n/3 +1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
}
int x=5,y=15;
UIImageView* imgView1=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 93, 110)];
imgView1.image=[UIImage imageNamed:[imgarr objectAtIndex:indexPath.row + 0]];
x+=103;
UIImageView* imgView2=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 93, 110)];
imgView2.image=[UIImage imageNamed:[imgarr objectAtIndex:indexPath.row + 1]];
x+=103;
UIImageView* imgView2=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 93, 110)];
imgView2.image=[UIImage imageNamed:[imgarr objectAtIndex:indexPath.row + 2]];
[cell.contentView addSubview:imgView1];
[cell.contentView addSubview:imgView2];
[cell.contentView addSubview:imgView3];
return cell;
}
Use #autoreleasepool. Here are details about #autoreleaspool
The #autoreleasepool statement is doing the same job as before, instead of using the NSAutoreleasePool class. The way the NSAutoreleasePool worked was a bit weird, as creating it caused an effect throughout the whole application; #autoreleasepool creates a scoped area and makes it clearer what's within the pool and when it drains (when it goes out of scope). It's also more efficient according to Apple.
The concept of an autorelease pool is simple, whenever an object instance is marked as autoreleased (for example NSString* str = [[[NSString alloc] initWithString:#"hello"] autorelease];), it will have a retain count of +1 at that moment in time, but at the end of the run loop, the pool is drained, and any object marked autorelease then has its retain count decremented. It's a way of keeping an object around while you prepare whatever will retain it for itself.
With ARC, whilst the autorelease keyword isn't used by the developer, the underlying system that manages ARC inserts that for you. (Remember: All ARC is doing is inserting retain, release and autorelease calls for you at the appropriate times). Because of this, the existing AutoreleasePool concept needs to stay around.
If you remove the autorelease pool, your objects will start leaking
For more details check here

UICollectionView showing wrong layer when scrolling

I know it's pretty common question. I've seen number of people ask here this type of a question, but I still can't seem to get it right.
I have a UICollectionView with pictures. I'm the getting the images from facebook so I'm using SDWebImage to load them.
Until here everything works ok.
The user can upload pictures that he took. so until the uploading process is finished and image was uploaded to facebook, I want the local image to be shown but with a layer (different alpha, activity etc') and once i get a notification from the server, the new image from facebook is replacing the local one.
My problem is that after adding the local images to the collectioView with the layer, more cells are displaying this layer and from time to time when i scroll up , the new images changes.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
PictureCell* cell = (PictureCell *)[_collectionView dequeueReusableCellWithReuseIdentifier:[PictureCell reuseIdentifier] forIndexPath:indexPath];
Picture *picture=(Picture *)[_campaign.pictures objectAtIndex:indexPath.row];
//If the picture is being uploaded, add the image from the local image property and set the blur layer
if (!picture.facebookPicture) {
cell.pictureImageView.image = picture.image;
[cell TaggleBlur:YES];
}
else
{
[cell.pictureImageView sd_setImageWithURL: [NSURL URLWithString:picture.facebookPicture.source ]placeholderImage:[UIImage imageNamed:#"actionBar_pico_icon"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSArray *visibleIndexPaths = [_collectionView indexPathsForVisibleItems];
if (error==nil) {
if ([visibleIndexPaths containsObject:indexPath]) {
cell.pictureImageView.image = image;
[cell TaggleBlur:NO];
}
}
}];
}
return cell;
}

App crashes due Memory Warning with heavy images

I have a quite big NSArray with almost 100 images, I'm displaying these images in a table view. My problem is that after I scrolled through the array, my project crashes due Memory Warning. I'm getting no errors or other issues just Xcode logs Received Memory Warning message 4-5 times and then the app crashes.
I've tried to use didReceiveMemoryWarning method to solve the problem, but this solution doesn't helped.
- (void)didReceiveMemoryWarning {
arrayOfImages = nil;
}
I also tried it in the viewDidUnload method at least to clear the memory when the user goes to another view, but it didn't worked too.
- (void)viewDidUnload {
arrayOfImages = nil;
[super viewDidUnload];
}
How could I solve this problem? I need to use that images, but it's really annoying that it crashes after the user checked 40-50 images.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *tableIdentifier = #"imgCell";
BTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:tableIdentifier];
cell.imgView.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#.jpg", [arrayOfImages objectAtIndex:indexPath.row]]];
return cell;
}
From the documentation for imageNamed:,
This method looks in the system caches for an image object with the
specified name and returns that object if it exists… 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.

How to properly load images on custom UICollectionViewCell?

I'm at this all afternoon and I can't figure it, so your help would be awesome! I have a custom UICollectionViewCell which I'm populating with images I create on the fly. The paths of the images are stored in Parse and after the query is done, I call [self.collectionView reloadData].
In my cellForItemAtIndexPath I have the following code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MovieCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"original_title" ascending:YES];
[self.moviesforWatchlist sortUsingDescriptors:[NSArray arrayWithObjects:sort, nil]];
cell.userInteractionEnabled = YES;
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[cell addSubview:spinner];
NSDictionary *movie = self.moviesforWatchlist[indexPath.row];
NSString *moviePosterPath = movie[#"poster_path"];
NSString *posterURL = [NSString stringWithFormat:#"http://image.tmdb.org/t/p/w342%#", moviePosterPath];
NSURL *posterImage = [NSURL URLWithString:posterURL];
UIImage *cachedImage = [self.imageCache objectForKey:posterImage];
if (cachedImage) {
cell.imageView.image = cachedImage;
}
else if (cell.imageView.image == nil) {
cell.imageView.image = [UIImage imageNamed:#"default_collectionviewcell.jpeg"];
dispatch_queue_t downloadQueue = dispatch_queue_create("get image data", NULL);
dispatch_async(downloadQueue, ^{
[spinner startAnimating];
NSData *imageData = [NSData dataWithContentsOfURL:posterImage];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
MovieCollectionViewCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell) {
updateCell.imageView.image = image;
[updateCell reloadInputViews];
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
}
});
[self.imageCache setObject:image forKey:posterImage];
});
}
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
return cell;
}
I'm calling the query method like this:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self getWatchlistData];
[self.collectionView reloadData];
}
This results in a flashing of images until the images are loaded on to UICollectionViewCells. I want the images displayed in my collectionviewcell to be in alphabetical order, so I'm sorting my array that holds the paths to do so, in cellforitemsatindexpath is this whats causing the flashing of images? Or is it something else? Your help is much appreciated!
There are a number of things I would change:
I wouldn't sort your array in cellForItemsAtIndexPAth. That method is called as each cell is displayed on screen, so you are potentially resorting it numerous times when that is not necessary. If your data is changing between one cell being created and the next, then the re-sorting is likely to mess up the link between the collectionView indexPath.row and your array index. I would sort the array when it is populated, perhaps in your viewWillAppear or getWatchlistData methods. If the data does change after this, use some of the other collectionView delegate/datasource methods to handle inserts/deletes/moves. But I don't think this is causing the flickering of images.
In the code which you post back to the main queue, you call the cellForItemAtIndexPath method, update the cell image and call reloadInputViews (which presumably reconfigures the cell in some way). But you then call reloadItemsAtIndexPaths for all visible cells. What this will do is cause your collectionView to re-call the cellForItemAtIndexPath datasource method for each visible item, causing the image for each to be reloaded from the cache. I think this is the cause of your flickering. I would just save the downloaded image to the cache, and then call reloadItemsAtIndexPaths for just the one indexPath that's affected. The cellForRowAtIndexPath datasource method will be called, and will recover the image from the cache and add it to the new cell.
Likewise, I think the reloadItemsAtIndexPaths at the end of your cellForItemAtIndexPath is superfluous and is likely to contribute to the flickering.
I think you are at risk of some cell reuse problems - for example, you add the spinner to every cell. But the dequeued cells might have been displayed before and therefore already have a spinner (albeit stopped and so probably hidden). If you scroll back and forth a lot, you might end up with cells with loads of spinners, wasting memory. I would give the spinner a tag before adding it as a subView, and then use viewWithTag to determine whether a spinner has already been added to the dequeued cells (if so, remove it). And only add the spinner if necessary (i.e. in your if ...image == nil clause).
Perhaps unlikely, but if the dequeued cell has a default image (so cell.imageView.image != nil) and is being reused for a cell for which the image has not yet been cached (so cachedImage == nil), then none of your dispatch methods will be executed, so the image will never be downloaded.
Sorry if that's a bit heavy, but I hope it's of use.
I have same issue, when any image got download then it flicks the view. I am going before like reload that cell, so it will call some delegate methods of collectionview. So i removed it and make a separate method for reload cell. Now when any new image got reload then i call call this method instead of reloadCellForIndexPath:. In custom method just copy paste code from your cellForItemAtIndexPath:. Assign cell in custom method.
CollectionPhotoItem cell = (CollectionPhotoItem)[self.collection cellForItemAtIndexPath:indexPath];
That's it. it will shows your cell without any flicks. Thanks.
try to use DLImageLoader
[[DLImageLoader sharedInstance] displayImageFromUrl:#"image_url_here"
imageView:#"UIImageView here"];
[[DLImageLoader sharedInstance] loadImageFromUrl:#"image_url_here"
completed:^(NSError *error, UIImage *image) {
if (error == nil) {
// if we have no any errors
} else {
// if we got an error when load an image
}
}];

Resources