views:

1397

answers:

4

Hi

I've read that imageNamed: is bad when trying to initialize images. But then what is the best way? I am using imageWithContentsOfFile: and passing the path of an image in my resources folder

[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"]

This call is made some 30 times in a for loop.

Now when I run my app with instruments, I see that a lot of memory is used up by NSString for operations like the above one where we use string literals (@"jpg") Instruments shows the responsible caller as [NSBundle mainBundle] and this in turn points to the line when I use the string literal for the type.

So what is the most effective way of initializing images without using too much of memory?

I changed the statement to

img = [UIImage imageWithContentsOfFile:[bndl pathForResource:fileName ofType:extn]]

where extn is static and initialized to @"jpg". fileName keeps changing for each iteration of the for loop. But even then the maximum use of NSString is because of [NSBundle mainBundle] and [NSBundle pathForResource:OfType:] according to Instruments.

+1  A: 

What you can do is to make sure that you release autoreleased objects within the loop

Before:

for (int i = 0; i < 1000; ++i) 
{   
   UImage* img = [UIImage imageWithContentsOfFile:
        [bndl pathForResource:fileName ofType:extn]];  
   ... 
}

After:

for (int i = 0; i < 1000; ++i) 
{   
   NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
   UImage* img = [UIImage imageWithContentsOfFile:
        [bndl pathForResource:fileName ofType:extn]];  
   ... 
   [ap release];
}

But I doubt that those NSString instances could cause too much trouble:

  1. The UIImages should take a much more memory than the strings (>= 100x).
  2. If your loop is only 30 iteration, and you get to the event loop the autorelease pool will be released, so you shouldn't see more than 30 strings alive. (Event without the AP in the loop)

Are you sure you're interpreting the output of Instruments correctly? Are you sure there aren't other parts of your code leaking those strings? (The code shown in question looks OK)

mfazekas
Thanks. I double-checked. Instruments points to the UIImage line and shows object-allocation for NSString. There is no NSString values in the line other than the image name and extension.
lostInTransit
Is there any way I can make sure I'm not missing anything?
lostInTransit
Are you checking leaks or object allocations?
mfazekas
I'm checking Object Allocations
lostInTransit
+2  A: 

imageNamed: is bad in some circumstances because it caches the image after loading it. So if you're going to be reusing the image—a pretty likely case for something in your app bundle—using imageNamed: is perfectly fine. If you have a lot of different images, though, and you're only going to load a particular one occasionally, you'll want to avoid it.

If you don't want to use imageNamed:, the first piece of code is just fine. If you're worried about the temporary strings created in your loop, put this before the loop:

NSAutoreleasePool * pool = [NSAutoreleasePool new];

And this after:

[pool release];

That will ensure that any temporary objects within the loop are released once the loop exits. Make sure that any temporary objects you want to keep are retained, however. (For example, the images themselves need to be either added to a data structure that will retain them, such as an array, dictionary, or set, or retained manually.)

Brent Royal-Gordon
+4  A: 

I'd avoid using autoreleased objects where you can within a loop. If Instruments is reporting a lot of hits on the NSBundle pathForResource:ofType: call, I'd pull some of that processing outside of the loop.

My suggested implementation would look something like this:

NSString *resourcePath = [[[NSBundle mainBundle] resourcePath] retain];

for (int i = 0; i < 1000; ++i) 
{   
 ...

 NSString *pathForImageFile = [resourcePath stringByAppendingPathComponent:fileName];
 NSData *imageData = [[NSData alloc] initWithContentsOfFile:pathForImageFile];

 UIImage *image = [[UIImage alloc] initWithData:imageData];
 [imageData release];

 ... 

 [image release];
}

[resourcePath release];

You will be accumulating one autoreleased string (pathForImageFile), but that shouldn't be so bad. You could create and release an autorelease pool within the loop, but I would suggest doing that at most once every 10 or 100 loop passes, not every pass. Also, the retain and release on resourcePath may be superfluous, but I put it there in case you want to use your own autorelease pool somewhere in here.

Brad Larson
A: 

I haven't seen anyone mention it yet, but the reason you're seeing multiple NSString instances being created by that line is because pathForResource:ofType: has to concatenate strings together to create a full path from the various components (directory name, filename, extension).

I'm firmly in the "don't worry about it" camp, as well. Compared to the memory use for even a very small image, a few dozen NSString instances is just noise. If your loop goes from 30 images to a few thousand, or something, then you might want to consider creating an NSAutoreleasePool inside the loop.

Mark Bessey