I've got a fairly simple app that has the following in the view the program is mostly in:
int currentPageIndex;
NSArray *images;
NSString *nextImage;
IBOutlet UIImageView *firstPage;
IBOutlet UIButton *bigButton;
In the implementation viewDidLoad, I load the array with a bunch of image file names:
images = [NSArray arrayWithObjects:#"image1.jpg", #"image2.jpg", etc, nil];
[images retain];
Each time the bigButton is tapped, the image changes:
- (IBAction)bigButtonTapped:(id)sender {
currentPageIndex++;
nextImage = [images objectAtIndex:currentPageIndex];
firstPage.image = [UIImage imageNamed:nextImage];
}
Everything works as I want it to, except that I am getting a "Received memory warning. Level=1" in the console with my device plugged in. This warning comes up after every 12 images or so, and eventually it crashes with "EXC_BAD_ACCESS"
I thought this would actually be a good way not to put anything in memory, as there is only one UIImageView on the screen and its image is changed as I need it to be.
It is a very simple app so I'm sure the fix is very simple... any ideas what I might be overlooking? Thanks so much!!
Since you get a memory warning, the problem must be that the images aren't released. However, in the code you show, you're handling the images correctly. So the problem is most likely in a part of the code you're not showing us.
The only minor problem is see, which has been mentioned before, is that the currentPageIndex will eventually point outside of the range of the array. But this will cause a different error.
To avoid going over the size of the Array,
currentPageIndex= 0;
for(currentPageIndex in images){
doStuff;
}
OH!!!!! I THINK I spotted the your problem. Whenever you use UIImage imageNamed to load images all the images stay in memmory even though release reference to it. use the other UIImage method:contentOfFile.
One other thing, make sure your images are optimize for iOS. Use .png when posibible.
dibu2z
I assume Image is a retained property.
Try to release it at the beginning of your bigButtonTapped.
Hope it helps.
Could be that you've reached the end of the array and you're trying to access past the end of the array. You could do a
currentPageIndex++;
if ( currentPageIndex < [images count]) {
nextImage = [images objectAtIndex:currentPageIndex];
firstPage.image = [UIImage imageNamed:nextImage];
}
Also could be that the image you listed doesn't exist in the bundle.
There isn't really enough information here to say for sure what your problem is. EXC_BAD_ACCESS generally happens when you try to access an objects that has already been deallocated.
The quickest way to track down the real cause of EXC_BAD_ACCESS is by using the NSZombieEnabled executable argument, and then setting a breakpoint on objc_exception_throw. This will get you a stack trace, and allow you to determine specifically which object you are trying to access.
http://www.cocoadev.com/index.pl?NSZombieEnabled
Using Malloc to debug
Related
I've been reading everything I can find on here about this topic but am still not sure the best way to proceed. I have a heavy UIImageView that uses an array of fat UIImages acting as an animated loop. This UIImageView is serving as the background for every screen in the app. (Client's request, not mine, so don't hate.) We've optimized the png files as small as they can go but it's still a pretty heavy load.
I've read several posts about how UIImage searches the cache for an existing image of that name (Ex. Shared UIImageView for background image throughout app - Singleton Property) but this doesn't seem to be happening. I've also been reading here about singletons and instantiating in the appdelegate but I'm not sure if either of these are the right way to proceed.
What's the best way to load the UIImageView once over the life of the app and use it in the background of every viewcontroller? Btw, because it takes several seconds to load, I'm going to be adding a "loading" page at app start that uses a single static image and an activity indicator.
Also, I'm not as familiar with testing and performance tools. Which one should I be using to test performance and make sure this is what is causing the hesitations throughout the app?
Apologies in advance for the noob questions - I generally avoid asking questions at all but sometimes, as in this case, I don't even know where to begin the research.
I'm rather pleased with myself. I went with the AppDelegate technique and it's working beautifully. I declared a UIImageView property in the AppDelegate, then add the image code to its getter:
- (UIImageView *)backgroundImageView {
if (!_backgroundImageView) {
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:150];
for (int imageNum = 0; imageNum < 150; imageNum++) {
[tempArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"image_00%03i.png",imageNum]]];
}
_backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage animatedImageWithImages:tempArray duration:4.0]];
}
return _backgroundImageView;
}
This also allowed me to kill the object in applicationDidReceiveMemoryWarning.
Then I created a helper class that I can pop into all of my UIViewController classes:
+ (void)placeBackgroundImageUnderView:(UIView *)masterView {
myAppDelegate *appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
//Correct for shrinkage
appDelegate.backgroundImageView.frame = masterView.frame;
[masterView addSubview:appDelegate.backgroundImageView];
[masterView sendSubviewToBack:appDelegate.backgroundImageView];
}
This allowed me to add a single line in each viewDidLoad method:
[HelperClass placeBackgroundImageUnderView:self.view];
The thing is, just using UIImage alone should cache the whole thing and speed up load time. But it seems that every now and then the images would have to reload - memory issues? So this allows me more control over it by instantiating the object once and using that same object, while also being to set that object to nil to free up memory if needed.
Since adding it, the whole app has been loading much faster. I added an additional load screen with an activity indicator for that initial load (also works beautifully) and everything after that is instant happy.
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.
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.
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.
I currently have this code:
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[imagesPath stringByAppendingPathComponent:[NSString stringWithFormat:#"/%#.png", [postsArrayID objectAtIndex:indexPath.row]]]];
It's loading in an image to set in a UITableViewCell. This obviously leaks a lot of memory (I do release it, two lines down after setting the cells image to be that image), and I'm not sure if it caches the image at all.
Is there another way, that doesen't leak so much, I can use to load in images multiple times, like in a tableView, from the Documents-directory of my app? Thanks.
The leaks tool or Instruments should tell you what exactly is leaking. However, the image and imageView properties retain their images, so you may need to ensure you're properly releasing them in the dealloc method for the UITableViewCell. But like AngeDeLaMort said, we really need more information to give you a precise answer.
What is leaking exactly?
If you alloc an image and release it after, I don't see the leak your are talking about? Maybe more code or more precision would help.