[UIImage imageNamed:]

December 10, 2009 · Posted in dev 
UIImage *myImage = [UIImage imageNamed:@"pony.png"];

This is probably one of the most convenient methods I’ve ever come across. Feed it a name and it will return an image. Better yet, it handles caching for you automatically so that you don’t have to. Notice something about it though? There’s no alloc, meaning you shouldn’t call release. If you can’t call release, how do you get your memory back? This isn’t a big deal when using Mac OS X’s older brother, NSImage, since your average computer these days ships with several gigabytes of memory as opposed to the paltry 25MB or so that most iPhone apps are limited to. If you’re dealing with a lot of images on the iPhone though, this hands-off caching approach is going to cause problems.

The first obvious solution is to just avoid the convenience method and manually alloc and load each image with its full path:

UIImage *myImg = [[UIImage alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/pony.png", [[NSBundle mainBundle] bundlePath]]];

That’s not awful for loading an image or two, but if you’re going to load many images, why not encapsulate this in something more elegant? Since such a method would be useful application-wide, it furthermore makes sense for the method to exist in its own application-wide class, rather than just stuffing the method willy-nilly into whatever class you’re working in. Actually, if you’re going to go to the trouble of creating a new class just for a convenience method, why not make it do other neat things?

// ImageServer.h

#import

@interface ImageServer : NSObject {

     NSMutableArray *imgArray;
     NSMutableArray *nameArray;
}

- (UIImage *)imageNamed:(NSString *)name;

@end

And:

// ImageServer.m

- (id)init
{

     if(self = [super init])
     {
          imgArray = [[NSMutableArray alloc] initWithCapacity:0];
          nameArray = [[NSMutableArray alloc] initWithCapacity:0];
     }

     return self;
}

- (UIImage *)imageNamed:(NSString *)name
{
     for(int i = 0; i < [imgArray count]; i++)
     {
          if([[nameArray objectAtIndex:i] isEqualToString:name])
               return [imgArray objectAtIndex:i];
     }

     UIImage *newImg = [[UIImage alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], name]];
     if(!newImg)
          return NULL;
     [imgArray addObject:newImg];
     [nameArray addObject:name];
     [newImg release];

     return [imgArray lastObject];
}

- (void)dealloc
{
     [imgArray removeAllObjects];
     [imgArray release];
     [nameArray removeAllObjects];
     [nameArray release];
     [super dealloc];
}

@end

So, what’s going on here? Well, you begin by allocating an ImageServer object, and then using it to grab images:

ImageServer *images = [[ImageServer alloc] init];
UIImage *myImage = [images imageNamed:@"pony.png"];

The second call should look familiar. Why not create a singleton though, would that not be less crude than allocating an object to… allocate more objects? Perhaps, but it’s worth examining what exactly the [ImageServer imageNamed:] method does. Each time the ImageServer instance loads a new image, it adds the name and image to the appropriate arrays; if the same image name is requested a second time, the ImageServer class can look it up and return the associated image, only allocating a new one if nothing is found. The reason this approach would fail as a singleton class is because doing so would defeat the whole purpose of writing this custom class, which is to have greater control over memory management.

In this non-exotic form, each UIViewController can allocate its own ImageServer, each of which maintains its own cache of images. When the UIViewController releases its ImageServer object, all of the cached data is flushed with it and your app regains that memory. If you want to flush the object but keep a certain image, it’s as easy as sending the image in question a retain message. ImageServers belonging to other UIViewControllers are unaffected however, meaning that the rest don’t have to reload their entire cache because everything was just thrown out.

As far as custom class solutions go, this is a very lightweight, simple solution. There are far more powerful, complex versions of essentially the same thing out there, but the important thing is it gets the job done, and it’s very extensible.

Addendum: Those with a bit more Cocoa know-how have probably realized that the code size can be cut down significantly by replacing the two NSMutableArrays with an NSMutableDictionary. The class is provided as-is in order to explicitly show what’s going on, but really, it could be more concise.

Comments

One Response to “[UIImage imageNamed:]”

  1. [...] memory the OS just dismisses it. If you're using a ton of images, try creating an image server ([UIImage imageNamed:] : The Daily Anvil) to store and release them. Also- I'm just looking for video output code and was curious what you [...]

Leave a Reply