UIButton setBackgroundImage consumes a lot of memory - iphone

I'm using the following code:
UIImage *buttonImage;
if (p.placeImage != nil) {
buttonImage = [UIImage imageWithData:p.placeImage];
} else {
buttonImage = [UIImage imageNamed:#"bg_place_noimg.png"];
}
[imageButton setBackgroundImage:buttonImage forState:UIControlStateNormal];
When executing the app with Instruments I can see the setBackgroundImage consumes a lot of memory. However, if I comment the last line it doesn't happen. Any possible reason?
EDIT:
If p.placeImage == nil and imageNamed:#"bg_place_noimg.png" is used memory usage is normal. p.placeImage is a Transformable value I use in Core Data to store images NSData downloaded from Internet.

I'm not surprised that commenting out the last line causes less memory to be consumed. When you set that image as the background of your button, the image is most likely retained by the button and so the image remains in memory. If you don't apply the image as the button background, the UIImage's retain count is 0 and so its memory can be reclaimed by the system if necessary.
Kristopher's theory about the difference between imageWithData and imageNamed is also correct. Check out the Discussion section for each of those initializers in the documentation for UIImage.

I'm not sure, but I would guess that your problem is that imageWithData: creates a whole new image each time, whereas the imageNamed: method returns the same image over and over again.
You may need to add some code to cache and reuse images that are identical. For example, maybe you could use a URL as a key into a dictionary of images, and only create new images for URLs that have not been loaded before.

Related

Asynchronous images - Meant for displaying images from web only?

I have images in my documents folder which I am displaying on one of my screens. It takes times to load the images and display them on the screen similar to when loading images from web. As far as I know asynchronous imageView works for the later case. I might be wrong.
Is there anyway we can display images from documents folder asynchronously?
Take a look at SDWebImage. It is a UIImageView subclass that lets you display image asynchronously from a URL and with a useful cache. It is designed to work with Internet URLs, but I think it will also go with internal URLs.
Put the loading of images in the background thread as following
-(void)backgroundLoadImageFromPath:(NSString*)path {
UIImage *newImage = [UIImage imageWithContentsOfFile:path];
[myImageView performSelectorOnMainThread:#selector(setImage:) withObject:newImage waitUntilDone:YES];
}
Then call that thread wherever you need to set the image
[self performSelectorInBackground:#selector(backgroundLoadImageFromPath:) withObject:path];
Note, in backgroundLoadImageFromPath you need to wait until the setImage: selector finishes, otherwise the background thread's autorelease pool may deallocate the image before the setImage: method can retain it.

Using UIImageView without being a memory hog?

UIImageView *mooshinLogo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mooshin.png"]];
I'm not sure if im instantiating my image correctly because i've been reading that imageNamed will not be emptied from the cache and should only be used for buttons and small icons. What about background images, and image galleries?
How do I implement UIImageView the right way?
imageNamed: method caches images that you load. In case of low memory conditions or memory warning that cache will be emptied.
If you want to show images in gallery it is better to use imageWithContentsOfFile: method as it doesn't cache the data.
In case of very big images you should use CATiledLayer to display your image using tiles.
No worries! The method imageNamed returns an autoreleased object, and it will do what you call "be emptied from cache" (i.e. its memory will be released) when it is not needed any more.
[UIImage imageNamed:(NSString *)imageName] is an autorelease convenience constructor. This means that it's retain count is increased when you initialize it and decreased at the end of the runloop. You could make 100 of them and they'd vanish from memory a few seconds later, unless they're retained by something else. By passing it to the UIImageView, the UIImageView will retain it and it will stay in memory only until the UIImageView is done with it, so you're doing that correctly, unless you're referring to the OS caching the image in "inactive" RAM. It may do that behind the scenes (and will know when to get rid of it), but you're certainly handling the object's lifecycle correctly. Since you're using the [[Class alloc] init...] way to construct your UIImageView, make sure you later call [mooshinLogo release] or [mooshinLogo autorelease].
(Please just ignore this if you already know.) Objective-C (at least, for iOS development) is a reference counted language. All objects start out with a reference count, or retain count, of 1, from the time they are alloc'd. From there, they can be retained ( [id retain] ), released ( [id release] ), or marked to be released at the end of the runloop ([id autorelease] ). Once the count is zero, they will be dealloc'd, but you should never concern yourself with it's actual retain count and only use objects you own (or are retaining).
When in doubt, you can check with Clang's Static Analyzer. It's finds probably 75% of the your leaks, and I've only had a handful of false positives. Either Build & Analyze, or Cmd+Shift+B.
Others above are right about imageNamed keeps a cache of these images. This is especially true when using NIB files. Releasing a Viewcontroller with ImageViews on them doesn't directly release the associated images.
I had an app that had lots of images with lots of pages in a navigation controller. Eventually it would crash when only using imageNamed method. So, I now use (found here on SO) and over:
+ (UIImage *)imageNamed:(NSString *)name {
//NSLog(#"ImageNamed: %#", name);
return [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:nil]];
}
Now I can control when I want to remove an image and and make it actually clear from memory. The aforementioned app, I would actually load the images on viewDidLoad and remove them on viewDidDisappear. So, when I was 10-15 pages deep in the NavController, I could keep memory down.

IOS / iPhone loading images into UIImage based on slider value memory issue

I am loading images into a UIImage with the values of a slider (these are pages or slides, if you like). This UIImage switches them very fast with the use of a slider. Problem is, at some point, the app crashes on the device with an error of:
2011-04-02 17:39:01.836 Book1[2123:307] Received memory warning. Level=1
Here's the code:
- (IBAction)slidePages:(id)sender{
int sliderValue = pageSlider.value;
NSString *slideToPage = [NSString stringWithFormat:#"P%i.jpg", sliderValue];
imagePlaceholder.image = [UIImage imageNamed:slideToPage];
pageDisplay.text = [NSString stringWithFormat:#"Page %i", sliderValue];
currentPage = sliderValue;
}
Is there anything I could do to make it more efficient? Maybe the error is somewhere else but I'm guessing it has to do with the fast loading of images.
Still, I don't know how iOS deals with this. Every time I load a new image into the UIImage what happens with the "unloaded" one?
Thanks in advance.
One imortant aspect of [UIImage imageNamed] is that it caches all images loaded in that way and they never get unloaded, even if you dealloc the UIImage that was created! This is good in some circumstances (e.g. smallish images in UITableView cells), but bad in others (e.g. large images).
The solution is to use [UIImage imageWithData] which does not do this caching and which unloads the data when the UIImage is dealloc'd.
More info and discussion here:
Difference between [UIImage imageNamed...] and [UIImage imageWithData...]?
Update
This question has some good info on the question of [UIImage imageNamed:] not emptying its cache when a memory warning occurs.
[UIImage imageNamed:] caches images, so every image thus loaded effectively leaks. I don't know what the canonical solution is, but one option might be to load the image with CGImageCreateWithJPEGDataProvider() and initialise it with [UIImage imageWithCGImage:].
The original (wrong) answer:
You may need to release the previous image before loading the current one.
Loading a bunch of large images in iOS has always been a memory issue. As Marcelo mentioned, you should only keep around images are you currently viewing. All other images should be released so they can be garbage collected and the memory freed up. In my experience, even loading 2 fairly large images (500k-2mb each) will cause memory issues, especially on older devices with less RAM.

Load UIImages correctly? Memory Management with "Leaks"

i´ve created a little game in which images are loaded when the user is touching the screen while moving. It depends on the screen position to load different images immediately.
I´ve got an UIImageView and because within the "touchesMoved" function, i´m loading the different images like this:
imageView.image = [UIImage imageNamed: [photos objectAtIndex: newImage ] ] ;
Now i want to improve my memory management using Instruments with "Allocations" & "Memory Monitor". Here´s i´m setting different snapshots with "Mark Heap" points and look for leaks. The line above is highlighted and now i want to know what´s wrong with it.. How can i improve that image-loading (without caching)?
Read this Dispelling the UIImage imageNamed: FUD
and also read the links in the question as well. Should answer everything you need.
First of all: imageNamed: does cache images.
If you're getting this line highlighted then there are 2 possible reasons (or both):
you're not properly releasing imageView
you're not properly releasing the photos array
+imageNamed returns an autoreleased object, yet the #property image in UIImageView retains it through its use until overwritten. -objectAtIndex: must return an object with a retain count of 1 which is not released.
If that indeed is the problem then the fix is
imageView.image = [UIImage imageNamed: [[photos objectAtIndex: newImage ] autorelease]];
However I doubt that is the real issue here.

UIImage, releasing the decompression cache?

I have an array of UIImages that contains some .jpg images downloaded from the net when the app starts. I also have a table view that shows some of these images in its cells. The problem is that when I scroll the table, the app's memory consumption always increases up to the point where the app crashes due to low memory conditions. The table cells seem to be reused fine, so my theory is the following.
Since the UIImageView in a table cell only retains one of the elements in the image array, when the cell gets reused, and a new image is assigned to it, the previously used image is of course not destructed (the cell releases it, but the array still retains). However, the decompression cache used to hold the raw image data (computed the first time the UIImage is displayed by a view) belongs to the UIImage itself, so it also remains. But I'm just guessing about all this.
Can this really be the problem? If so, how can I work around it? My first idea was to create a copy of the UIImage whenever it is assigned to a cell, but looks like UIImages can't be deep copied. Is there any way to tell a UIImage to keep the compressed jpg data in memory but throw away the decompression cache? (In the worst case I guess I can still download all the images and store them in local files, then load them from there and completely release the UIImages when not displayed anymore, but this doesn't seem to be an elegant solution.)
--
I can't post the original code (as suggested in comments) as it is fairly complicated with custom table cells and custom views and even a background thread downloading the images, but I've just created a small test app and it seems to show the same behavior. Here's a little function that is called when the user taps a UIButton:
- (IBAction)onNext:(UIButton*)sender
{
static NSMutableArray* images = nil;
if (!images)
{
NSArray* names = [NSArray arrayWithObjects:
#"IMG_2957.JPG",
#"IMG_2962.JPG",
#"IMG_2965.JPG",
#"IMG_2970.JPG",
#"IMG_2971.JPG",
#"IMG_2978.JPG",
nil];
images = [NSMutableArray new];
for (int i = 0; i < names.count; ++i)
[images addObject:[UIImage imageNamed:[names objectAtIndex:i]]];
int i = 42;
}
static int current = 0;
imageView.image = [images objectAtIndex:current];
++current;
}
Yes, I know that the images array is leaking, that's not the point (in the original code I also want to retain the images for the entire lifetime of the app, and only release on quitting). But. According to Memory Monitor, after the first tap (all images get loaded, and the first one is displayed), the app consumes ~5MB (all jpgs loaded, one of them decompressed). After all subsequent taps, the memory consumption increases by ~2MBs (yep, I tested with 1024x768 images). So it looks like decompressed data of the previously displayed images is still there. I know this wouldn't be a problem if I released the image once it isn't visible anymore, and that would also release the decompressed buffer, but if possible, I'd like to retain all the images, and only release the decompressed data. Is that possible?
If you're reusing cells, make sure those cells implement:
-(void)prepareForReuse
{
// releasing the image
self.imageView = nil; // Or release depending on you memory strategy
self.labelView = nil; // get them all
}
I don't think UIImage has no memory leaks that fatal. I have been working on few applications which has to deallocate UIImage a lot due to memory constraint, but it's still working fine. I believe your image leaks somewhere. Show us some code so someone will point that out for you.