UIImageView Leak / Autorelease - iphone

I am running a fairly memory intensive loop to generate images and have come unstuck in memory leaks / the autorelease retaining memory allocations for too long.
Can anyone please explain exactly what is being held and autoreleased below? I have run this through the Allocations instrument and it increases in size until the loop finishes and deallocates all of the autorelease objects (as I understand it from 3 days of trial and error). This is ok for less loops but when I exceed 200 it eventually crashes before it gets to autorelease. By commenting out the following code, this increase stops and the Instruments graph stays horizontal with a set amount of memory:
for (int l=0;1 < 300; 1++) {
UIImage * Img = [[UIImage alloc] initWithContentsOfFile:Path]; //Path is a NSString pointing to bundlePath and a sample image
UIImageView *ImgCont = [[UIImageView alloc] initWithImage:Img];
//here I usually add the view to a UIView but it is not required to see the problem
ImgCont.frame = CGRectMake(x, y, w, h);
[ImgCont release];
[Img release];
}
I have tried wrapping this with a NSAutoreleasePool without success - any ideas what I'm doing wrong?
Thanks,

When you add the imageView to a view, it's retained by that view, so even if you release Img and ImgCont, they still exist, and you are left with 300 objects.
Also, and I'm not completely sure about this, but if you are using the same image over and over, you should use [UIImage imageNamed:NAME], since it reuses the images, something I can not say for [UIImage initWithContentsOfFile:PATH]; (If the OS doesn't optimize that case, right now you have the same image 300 times in the memory).

None of the objects you are explicitly creating are being autoreleased so it must be stuff inside those UIKit calls you have. There's not a lot you can do about that though in terms of cutting down the number of autoreleases. But what you can do is mess around with autorelease pools.
You say you've tried NSAutoreleasePool but have you tried wrapping each iteration of the loop in a pool like so:
for (int l=0;1 < 300; 1++) {
#autoreleasepool {
UIImage * Img = [[UIImage alloc] initWithContentsOfFile:Path]; //Path is a NSString pointing to bundlePath and a sample image
UIImageView *ImgCont = [[UIImageView alloc] initWithImage:Img];
//here I usually add the view to a UIView but it is not required to see the problem
ImgCont.frame = CGRectMake(x, y, w, h);
[ImgCont release];
[Img release];
}
}
Although you should think about not doing it exactly like that, because it's possibly overkill. But I suggest you try that and if you're still having problems, then it's not this loop.

Related

Better way to initialize UIImageView with non-zero origin

Creating UIImageView with some offset is quite common task when you're building interface in code.
I can see two ways to initialize UIImageView with origin not equal to (0,0):
First way requires only image filename and origin, but contains a lot of code (we can reduce number of lines by one using frame.origin = CGPointMake(x,y); ):
UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image_filename"]];
CGRect frame = imgView.frame;
frame.origin.x = 150;
frame.origin.y = 100;
undoBg.frame = frame;
Second way has much less code, looks cleaner but we need to hardcode image size:
UIImageView *shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 150, 800, 600)];
shadowView.image = [UIImage imageNamed:#"image_filename"];
What is best practice for you and why?
Thanks.
Hardcoding the images sizes is a form of Unnamed numerical constants which is an indication of Code Smell
This sort of thing should be avoided as much as possible as it can generate code that is a lot harder to maintain and is prone to human introduced errors. For example what happens when your graphic artist changes the size of the image? Instead of changing just one thing (the image) you now have to change many things (the image, and every place in the code where the image size has been hard coded)
Remember that you code not for today, but for the people who will come after you and maintain your code.
If anything, if you were really concerned about the extra lines of code, then you would abstract loading the UIImageView into a category, so that it can be used everywhere (note that this code is not tested):
#interface UIImageView (MyExtension)
-(UIImageView*)myLoadImage:(NSString*)named at:(CGPoint)location;
#end
#implementation
-(UIImageView*)myLoadImage:(NSString*)named at:(CGPoint)location
{
UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:named]];
CGRect frame = imgView.frame;
frame.origin.x = location.x;
frame.origin.y = location.y;
return imgView;
}
#end
Then you could simply do:
UIImageView* imageView = [UIImageView myLoadImage:#"image_filename" at:CGPointMake(150,100)];
I use the second one with slight modification,
UIImageView *shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 150, 800, 600)];
shadowView.image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:extension] ];
because imageNamed: caches image and cause memory leak.
I usually want my code to be easily readable. On the other hand I want the job done as fast as possible. In this case, there is so little code, I would go with less code. This is because I can get understand it so fast anyways. If it would be a much bigger example, I would use the easily readable code.
Surely it depends on your requirements. If I need to create an imageView in a class where the offset may change then I might do something like:
int myX = 10;
int myY = 100;
int myWidth = 200;
int myHeight = 300;
UIImageView *shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(myX, myY, myWidth, myHeight)];
shadowView.image = [UIImage imageNamed:#"image_filename"];
but if I don't need to vary the offset and I know for a fact that the value won't change and no-one else will be needing to read or re-use my code then there's maybe nothing wrong (imho) with just using numbers in place of the int vars.
btw, you might want to avoid imageNamed as it caches the image which can lead to leaks.

Adding thumbnail size images to tableview

I have a tableview, and i am loading images to it. I have images which are ranging from 150kb - 2MB. Since this is too much for a tableview to handle (it takes long time to load, and makes the scrolling slow), i thought of using ImageIO framework to create thumbnail images of images.
I found a code that does this, but i can't undestand it.
1.) Can someone please explain me the code
2.) My problem is that, I have a tableview and i need to load thumbnail images to it. So how can i use the following code and display it on my tableview. Can someone show me some sample code or a tutorial that does this ?
heres the code ;
-(void)buildGallery
{
for (NSUInteger i = 0; i < kMaxPictures; i++)
{
NSInteger imgTag = i + 1;
NYXPictureView* v = [[NYXPictureView alloc] initWithFrame:(CGRect){.origin.x = x, .origin.y = y, .size = _thumbSize}];
NSString* imgPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"%d", imgTag] ofType:#"jpg"];
CGImageSourceRef src = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:imgPath], NULL);
CFDictionaryRef options = (CFDictionaryRef)[[NSDictionary alloc] initWithObjectsAndKeys:(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform, (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageIfAbsent, (id)[NSNumber numberWithDouble:_maxSize], (id)kCGImageSourceThumbnailMaxPixelSize, nil];
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options); // Create scaled image
CFRelease(options);
CFRelease(src);
UIImage* img = [[UIImage alloc] initWithCGImage:thumbnail];
[v setImage:img];
[img release];
CGImageRelease(thumbnail);
}
}
Basically the problem you have is due to the fact that when you scale down an image, the number of bytes stored in memory doesnt change when you scale it down. The hardware still has to read your 2mb image, and then render it to a smaller scale. What you need to do is to either change the size of your image (use photoshop or other) or the way im suggesting is to compress your image, and then scale it down. The image will look rough at normal size, but will look ok when you scale it down to a thumbview.
To generate an NSData version of your image encoded as a PNG.
NSData *PNGFile = UIImagePNGRepresentation(myImage);
Or a JPEG, with a quality value set
NSData *JPEGFile = UIImageJPEGRepresentation(myImage, 0.9f);
Both of these will give you an image smaller than you currently have, which will be easier to render in the tableView.
In order to get better performance you're going to have to load the image in a background thread, and after it's in memory add the UIImage to the image view on the main thread. There are a couple ways to go about doing this, but the simplest is going to be using GCD's block based methods.
Resizing the image is definitely still important for memory considerations, but get the asynchronous image loading part down first.

Preloading a UIImageView animation using objective c for the iphone

I have an animated image which works great. It consists of 180 high quality images and it plays fine and loops continuously. My problem is that the first time I load the view containing these images it takes a long time to load. Every subsequent time after that it loads immediately as I am assuming that the images have been cached or preloaded!!! I come from a flash background and as I am sure you aware preloaders are as common as muck so I don't feel this should be difficult to find but after countless googling I cannot find any good examples on preloading or any articles on why there is a delay and what to do about it.
So my question(s) is this:
Is there a checkbox in the info.plist to preload all my images at the start of the app?
How can you preload images and are there any simple example projects that I could look at?
Is this the best way to implement what is essentially a video but has been output to a png sequence?
Is there another method as viewDidLoad does not work as I expect it to do. It traces "FINISHED LOADING IMAGES" (see code below) but the view does not show for a second or two after the images have been loaded so if the view does not show until the images have loaded then neither will the UIActivityIndicatorView which is also in the same view.
How do you do event listening in objective c?
Below is the code in the viewDidLoad which I believe is fairly standard:
Any help is greatly appreciated as I am banging my head on a brick wall on something that seems so basic in ui development. Help :)
- (void)viewDidLoad {
[super viewDidLoad];
imageArray = [[NSMutableArray alloc] initWithCapacity:IMAGE_COUNT];
NSLog(#"START LOADING IMAGES");
// Build array of images, cycling through image names
for (int i = 0; i < IMAGE_COUNT; i++){
[imageArray addObject:[UIImage imageNamed: [NSString stringWithFormat:#"Main_%d.png", i]]];
}
animatedImages = [[UIImageView alloc] initWithFrame:CGRectMake(0,20,IMAGE_WIDTH, IMAGE_HEIGHT)];
animatedImages.animationImages = [NSArray arrayWithArray:imageArray];
animatedImages.animationDuration = 6.0;
animatedImages.animationRepeatCount = 0;
[self.view addSubview:animatedImages];
animatedImages.startAnimating;
[animatedImages release];
NSLog(#"FINISH LOADING IMAGES");
}
Cheers
M
In case someone finds this question, I have an answer, which is to pre-render the images like this.
NSMutableArray *menuanimationImages = [[NSMutableArray alloc] init];
for (int aniCount = 1; aniCount < 21; aniCount++) {
NSString *fileLocation = [[NSBundle mainBundle] pathForResource: [NSString stringWithFormat: #"bg%i", aniCount + 1] ofType: #"png"];
// here is the code to load and pre-render the image
UIImage *frameImage = [UIImage imageWithContentsOfFile: fileLocation];
UIGraphicsBeginImageContext(frameImage.size);
CGRect rect = CGRectMake(0, 0, frameImage.size.width, frameImage.size.height);
[frameImage drawInRect:rect];
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// then add the resulting image to the array
[menuanimationImages addObject:renderedImage];
}
settingsBackground.animationImages = menuanimationImages;
I have tried multiple other methods of pre-loading images, and this is the only thing I've found that works.
My problem is that the first time I load the view containing these images it takes a long time to load. Every subsequent time after that it loads immediately as I am assuming that the images have been cached or preloaded
you are right at this point ...... as you are using method imageNamed: for this method document quotes.....
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.
so in my opinion, rather than doing following stuff in viewDidLoad, you should do it earlier where delay is of not considerable......
for (int i = 0; i < IMAGE_COUNT; i++)
{
[imageArray addObject:[UIImage imageNamed: [NSString stringWithFormat:#"Main_%d.png", i]]];
}
another approach
- (void)spinLayer:(CALayer *)inLayer duration:(CFTimeInterval)inDuration
direction:(int)direction
{
CABasicAnimation* rotationAnimation;
// Rotate about the z axis
rotationAnimation =
[CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// Rotate 360 degress, in direction specified
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 * direction];
// Perform the rotation over this many seconds
rotationAnimation.duration = inDuration;
rotationAnimation.repeatCount = 100;
//rotationAnimation.
// Set the pacing of the animation
//rotationAnimation.timingFunction =
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// Add animation to the layer and make it so
[inLayer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
this method will help in animation call it as follow(I am assuming that you are putting above method in same class where you have imageView.
[self spinLayer:yourImageView.layer duration:5.0
direction:<-1 or 1 for anti clockwise or clockwise spin>];
remember just set only one image to that imageView(which you wish to animate.
thanks,

Loading more than 10 images on iPhone?

I'm trying to add more than 10 pictures on ScrollView.
NSUInteger i;
for (i = 1; i <= numberOfImage; i++)
{
NSString *imageName = [NSString stringWithFormat:#"d%dimage%d.png", imageSection, i];
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[scrollView addSubview:imageView];
[imageView release];
}
This code is from Apple example and it works fine. But if the variable 'i' is bigger than 10, 'UIImage *image' is empty. The imageName seems to correct. But I don't know why it does not load image. Does anybody sees the problem??
And one more thing. If I do like that, does iOS controls memory automatically? I mean it's kind of wasting memory if all (more than 10) images are loaded on memory even they are not displayed. I've heard that iOS loads images only displayed on screen and free images those are not displayed. Is that right?
Thanks for reading.
UIimage imageNamed: does cache file contents. I recommend you to use UIImage +imageWithContentsOfFile: that doesn't cache at all in such situation.
You have to make sure the images have the correct name (like 0dimage11.jpg) and are added to the XCode project.
You probably have to set the contentSize accordingly.
IOS will not do that magic memory management thing unless you are using a CATiledLayer based UIView.
If UIImage is not created, it because the name does not refer to an image in the resource folder and you should have an exception.
You need to have the correct file names. You said you think the file names are correct.
NSLog(#"Loop %d: d%dimage%d.png", i,imageSection, i]);
Log out the file names so you can see what the names actually are. Place that line in your loop.
NSUInteger i;
for (i = 1; i <= numberOfImage; i++)
{
NSString *imageName = [NSString stringWithFormat:#"d%dimage%d.png", imageSection, i];
NSLog(#"Loop %d: d%dimage%d.png", i,imageSection, i]);
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[scrollView addSubview:imageView];
[imageView release];
}
Then monitor the filenames in the debugger and see if those image files exist, and in the same directory where Apple put their image files.
Hey,Its not good way to load 10 images at once.All things you have done correct and still you'r image display empty then please check log may be there is memory warning.You can you apple's sample code photoscroller. It will do all thing that you want and also manages good memory.there are two method one is using CATieldLayer and another one directly load images. I recommended you to use method that uses CATieldLayer.
Sorry guys. This is turned out to be my fault. well, not exactly MY fault. Designers throw me many files with wrong filenames like d1imgae11.png... Anyway tips from all you guys gave me different view to see the problem and I got another hint about not to cache images. Thanks.

(iphone) am I creating a leak when creating a new image from an image?

I have following code as UIImage+Scale.h category.
-(UIImage*)scaleToSize:(CGSize)size
{
UIGraphicsBeginImageContext(size);
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
// is this scaledImage auto-released?
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
I use image obtained as above and use it as following.
UIImage* image = [[UIImage alloc] initWithData: myData];
image = [image scaleToSize: size]; <- wouldn't this code create a leak since
image(before scaling) is lost somewhere?
i guess above codes work fine if image was first created with auto-release.
But if image was created using 'alloc', it would create a leak in my short knowledge.
How should I change scaleToSize: to guard against it?
Thank you
EDIT -
I'd like to use alloc(or retain)/release on UIImage so that I can keep the # of UIImage in memory at a point small.
(i'm loading many UIImages in a loop and device can't take it)
Notice that your code could be rewritten as:
UIImage *image = [[UIImage alloc] initWithData:myData];
UIImage *scaledImage = [image scaleToSize:size];
image = scaledImage;
so let’s see what happens:
image is obtained via alloc, hence you own that object
scaledImage is obtained via a method that returns an autoreleased object since UIGraphicsGetImageFromCurrentImageContext() returns an autoreleased object
you own the original image but you don’t own scaledImage. You are responsible for releasing the original image, otherwise you have a leak.
In your code, you use a single variable to refer to both objects: the original image and the scaled image. This doesn’t change the fact that you own the first image, hence you need to release it to avoid leaks. Since you lose the original image reference by using the same variable, one common idiom is to send -autorelease to the original object:
UIImage *image = [[[UIImage alloc] initWithData:myData] autorelease];
image = [image scaleToSize:size];
Or, if you’d rather release the original image instead of autoreleasing it,
UIImage *image = [[UIImage alloc] initWithData:myData];
UIImage *scaledImage = [image scaleToSize:size];
[image release];
// use scaledImage from this point on, or assign image = scaledImage
IMO, it doesn’t make sense to change scaleToSize:. It is an instance method that creates an (autoreleased) image based on a given UIImage instance. It’s similar to -[NSString stringByAppendingString:], which creates a (an autoreleased) string based on a given NSString instance. It doesn’t and shouldn’t care about the ownership of the original string, and the same applies to your scaleToSize: method. How would the method know whether the caller wants to keep the original image?
I’d also rename scaleToSize: to imageByScalingToSize to make it similar to Cocoa’s naming convention — you’re getting an image by applying an operation to an existing image.
Yeah, it is sure that you have a leak. The object stored previously in the image is not referenced anymore but not deallocated yet