Programming

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:-

-(UIImage*)loadImageNoCache:(NSString*)fileName
{
	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

iPhone: Modal Dialogs

Sunday 11 Apr 2010 19:48

If like me you come from a Windows programmer background then you might be surprised at the lack of support for modal dialogs in the iPhone OS. Apple provide you with a UIAlertView which is great for on screen prompts and confirmations, but using them can be quite tedious and require a lot more code than is often neccessary.

This means you can't have code like this

result = CreateModalDialog(NSTitle, NSMsg, @"Ok", @"Cancel");
switch(result)
{
   ....
}

Instead you have to provide delegates which are called once the user selects an option. In other words your code does not stop and wait for a return value. Whilst this has its benefits in terms of multitasking, your code can get quite messy as you chain these delegates together.

There is another way which involves creating a custom UIAlertView class and a run loop. The code should be self explanatory.

ModalDialog.h
UIAlertView *alert;   
    
@interface MyUIAlertViewDelegate : NSObject <UIActionSheetDelegate, UIAlertViewDelegate> 
{
  int result;
}

-(int)getResult;

@end;

ModalDialog.mm
@implementation MyUIAlertViewDelegate

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
	result = buttonIndex;
}

-(int)getResult
{
	return result;
}

@end;

int CreateModalDialog(NSString *title,
                      NSString *msg, 
                      NSString *ok, 
                      NSString *cancel)
{
	// Create an instance of a custom UIAlertViewDelegate that we use to capture
	// the events generated by the UIAlertView
	MyUIAlertViewDelegate *lpDelegate = [[MyUIAlertViewDelegate alloc] init];

	// Construct and "show" the UIAlertView (message, title, cancel, ok are all
	// NSString values created earlier in your code...)
	UIAlertView *lpAlertView = [[UIAlertView alloc] 
initWithTitle:title
message:msg
delegate:lpDelegate
cancelButtonTitle:cancel
otherButtonTitles:ok, nil]; [lpAlertView show]; // By the time this loop terminates, our delegate will have been
// called and we can get the result from the delegate (i.e. what button was pressed...) while ((!lpAlertView.hidden) && (lpAlertView.superview!=nil)) { [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; Sleep(10); } // Grab the result from our delegate (via a custom property) int nResult = [lpDelegate getResult]; // Tidy up! [lpAlertView release]; [lpDelegate release]; NSLog(@"Result: %d", nResult); return nResult; } @end;

Tags: Modal Dialogs UIAlertView iPhone programming

iPhone: Creating a custom UIAlertView Busy Dialog

Wednesday 31 Mar 2010 17:59

One of the major problems facing iPhone developers, especially those coming from a Windows environment  is the lack of dialogs. We use dialogs when we require user input or need to display feedback.  I recently needed to display a 'Please Wait' dialog whilst downloading files from a web server, and found a way to do this using a UIAlertView.

The iPhone's UIAlertView generally displays an on screen message allowing the user to select one or more options as in the image below.

In the case of a "Please Wait" dialog we don't require any buttons and need to insert a UIActivity indicator inside the dialog. Generally speaking you can embed most UI elements inside a UIAlertView, but to do this you need to expand the dialog, which you can do by inserting a series of returns in the message body. In my case removing the buttons gives me ample room to insert the activity indicator.

UIAlertView *WaitPrompt()
{
  UIAlertView *alert = [[[UIAlertView alloc] 
    initWithTitle:@"Contacting Server\nPlease Wait..." 
    message:nil delegate:nil cancelButtonTitle:nil
    otherButtonTitles: nil] autorelease];
	
  [alert show];

  UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; indicator.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50); [indicator startAnimating]; [alert addSubview:indicator]; [indicator release]; // 250ms pause for the dialog to become active... int n=0; while(n < 250) { [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; Sleep(1); n++; } return alert; }

We can call the above routine to create our wait dialog, but how do we dismiss the dialog once we have completed our task?

void DismissWaitPrompt(UIAlertView *alert)
{
	[alert dismissWithClickedButtonIndex:0 animated:YES];
}

Please pay special attention to the while() loop at the end of the WaitPrompt() function. This section of code executes the main run loop for 250ms which is enough time for the OS to animate the UIAlertView onto the display. If you omit this section of code the chances are the dialog would not display until your processing is complete.

Tags: iPhone UIAlertView Custom programming

Programming Tips

Friday 12 Mar 2010 21:43

In my real life I've been a software engineer and C++ programmer for the past 20+ years! (A fact I was only reminded of today, and one that I don't like to contemplate for too long).

I've worked on many platforms including Windows, Windows Mobile, iPhone, DS, DSi and many more. I know from experience how difficult it can be to find good information and tutorials. I am not quite sure how this section is going to pan out, and if I will be posting tutorials or code snippits.

Time will tell...

Tags: programming iPhone Windows Mobile

Links

Sites of Interest