Select Article.

Site Search

Search Articles.

iPhone / iPad Memory Warnings & Crashes

Thursday 02 Sep 2010 18:28

So you've written your iPhone/iPad application and all seems fine except the seemingly random crashes which seem impossible to track down. You've run your program through Instruments and can't find any leaks and still you can't explain the crashes.

Finally you track down the problem to a shortage of memory when you discover your didReceiveMemoryWarning function is being called. But why does this happen sometimes and not others? Well, the truth behind the matter is that that of the 256KB or physical memory, only a certain percentage of this will be available when your program is run. The amount of memory will fluctuate wildly depending on which applications have been previously run since you powered on your device.

On the iPad for instance, you will have approx 100-110Mb of free memory available after you first boot your device. If however you launch Safari and open several web sites in different tabs the amount of memory available to your program can be as little as 15-20Mb. Clearly this is not enough for some applications and problems will inevitably arise.

The way the iPhone/iPad handle low memory situations is somewhat of a mystery, so I'll outline what I've discovered and how I managed to resolve my memory problems. When your program makes a request for memory and there is not enough physical memory to fulfil the task, the OS calls all resident programs didReceiveMemoryWarning function, and it is that programs responsibility to free off as much memory as possible. This at least is Apple's stance on the matter. The fact that Safari has stolen 80Mb for cached web pages is the real problem here, and you would think Safari would be asked to free off these cached pages before interfering with your program. However, in my case my program was receiving a memory warning, and then some point soon after that my program was terminated by the OS and I returned to springboard!

On investigation my program was allocating 20-30 images when the program was first started, and I was simply keeping a pointer to these images and freeing them off when the program exited (although not strictly necessary).

Because I came from a Windows programming background all this seemed quite a reasonable thing to do, but I've got news for you, garbage collection is not your friend, its your worst enemy. Now I'm an experienced programmer of 20 years, and never had to stop and think about when to release and not release memory, that was until I started programming the iPhone. Whilst there is alot to like about Apples OS and the SDK's, the way memory allocations are handled is not one of them.

Typically I was loading images with the following code...

UIImage *image = [UIImage imageNamed:@"button.png"];

This method actually caches images in memory, so if you are loading the same image multiple times it simply returns the cached image. This can be a good thing if you are using the image repetitively, but not a good idea if your program is an image viewer etc. The returned image is not necessarily removed from memory when you call [image release] so be careful.

If you allocate an image using imageNamed method, be sure to retain it or attach it to a button or something. If you don't it will float around in the cache and all will appear fine until you get a low memory warning, at which point the cached images are unloaded and pow, your program crashes.

Another way of loading images is as follows:

UIImage *image = [UIImage imageWithContentsOfFile:@"button.png"];

In many cases replacing imageNamed with imageWithContentsOfFile will result in your program being more stable, since images are not cached. However, images allocated this way are autoreleased, and calling [image release] results in an error. These images are autoreleased when you exit the current run loop, which in many instances is fine, therefore you don't have to explicitly release them.

However, if your program is an image viewer and you want to display a page of thumbnails you could run into problems.

for(int i=0; i<100; i++)
   UIImage *image = [UIImage imageWithContentsOfFile:filename];
   [image drawInRect:rect];

Take a look at the loop above. As you can see we load 100 images then render it in the current view. But when does this image get released? It doesn't, until you exit the for() loop and exit back out of the run loop in main(). So there is a point where we have 100 images loaded in memory at the same time!

So my advice is to load images as follows:-

	NSString *pathForImageFile = [NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] resourcePath], fileName];
	NSData *imageData = [[NSData alloc] initWithContentsOfFile:pathForImageFile]	UIImage *image =  [[UIImage alloc] initWithData:imageData];	
	[imageData release];
	return image;

Once you have finished with the image it is safe to call [image release].

So is there a safer way to handle cached images?

Yes, simply declare an NSMutableDictionary and add allocated images to it. This way you only reload images once and can free them all off with a couple of lines of code.

Allocate a dictionary with the following line:

thumbnailCache = [[NSMutableDictionary alloc] init];

Add images to the cache as follows:

- (UIImage*)loadImage:(NSString*)fileName
    UIImage *thumbnail = [thumbnailCache objectForKey:fileName];
    if (nil == thumbnail) 
        NSString *pathForImageFile = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], fileName];
        NSData *imageData = [[NSData alloc] initWithContentsOfFile:pathForImageFile];	
        UIImage *thumbnail =  [[UIImage alloc] initWithData:imageData];	
        [imageData release];
        [thumbnailCache setObject:thumbnail forKey:fileName];
    return thumbnail;

and finally free off the cached images as follows:

[thumbnailCache removeAllObjects];
[thumbnailCache release];

Finally I can't conclude this blog entry without mentioning Safari. So how can you reclaim all that memory occupied by cached browser pages? Well it turns out to be quite simple, and if you've read through this article you might already have realised how. Simply allocate as much memory as required in your Application Delegate (before the main window becomes visible) and then immediately free it all off again. This ensures that Safari and any other resident applications will free off memory in an attempt to provide you with your requested memory allocations. The reason you do this at the start of your program is so that when your didReceiveMemoryWarning is called your program has no cached memory to release, therefore it has to come from elsewhere in the system.

Hope this helped... if so let me know!

Tags: didReceiveMemoryWarning crash imageNamed Level 1

Comments (0)


Sites of Interest